diff --git a/.gitignore b/.gitignore index 6d712b30..cc40fcda 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ *.egg-info/ *.pdf *.png +!examples/output/png/ +!examples/output/png/*.png *.pyc *.swp .cache/ diff --git a/README.md b/README.md index 3c51fbbc..9a02cb83 100644 --- a/README.md +++ b/README.md @@ -131,8 +131,25 @@ Tweaking the plot is straightforward and can be done as part of your TeX work fl [The fantastic PGFPlots manual](http://pgfplots.sourceforge.net/pgfplots.pdf) contains great examples of how to make your plot look even better. -Of course, not all figures produced by matplotlib can be converted without error. -Notably, [3D plots don't work](https://github.com/matplotlib/matplotlib/issues/7243). +Of course, not all figures produced by matplotlib can be converted without error. +3D plots are supported for the common mplot3d artists exported by matplot2tikz, +including 3D lines, scatter plots, surfaces, wireframes, contours, bar charts, +quiver plots, custom ticks, and optional 3D axis-limit clipping. + +### 3D examples + +The example generator in [`examples`](examples) writes standalone PGFPlots files to +`examples/output/tex`. +The previews below were compiled with `pdflatex` and rendered from the generated +PDFs. + +| Line, scatter, text | Surface and wireframe | Contours | +| --- | --- | --- | +| ![3D line, scatter, and text](examples/output/png/line_scatter_text.png) | ![3D surface and wireframe](examples/output/png/surface_wireframe.png) | ![3D contour and filled contour plot](examples/output/png/contour_projection.png) | +| 3D bars with custom ticks | Semantic quiver | Log-scale clipping | +| ![3D bar chart with custom tick labels](examples/output/png/bar3d.png) | ![Semantic 3D quiver plot](examples/output/png/quiver3d.png) | ![Log-scale 3D clipping](examples/output/png/log_clipping.png) | +| No clipping | Hide outside limits | Clip to limits | +| ![Unclipped 3D surface and wireframe](examples/output/png/clipping_none.png) | ![3D surface and wireframe with outside geometry hidden](examples/output/png/clipping_hide.png) | ![3D surface and wireframe clipped to axis limits](examples/output/png/clipping_clip.png) | ## Installation diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..ce951d3f --- /dev/null +++ b/examples/README.md @@ -0,0 +1,18 @@ +# 3D examples + +Run the gallery generator from the repository root: + +```powershell +$env:PYTHONPATH = "src" +python examples/plot_3d_gallery.py +``` + +or on POSIX shells: + +```sh +PYTHONPATH=src python examples/plot_3d_gallery.py +``` + +The script writes standalone PGFPlots examples into `examples/output/tex/`. If +`pdflatex` and `pdftoppm` are available, it also compiles the examples and writes +PNG previews into `examples/output/png/`. diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 00000000..1994f5b4 --- /dev/null +++ b/examples/__init__.py @@ -0,0 +1 @@ +"""Example scripts for matplot2tikz.""" diff --git a/examples/output/png/bar3d.png b/examples/output/png/bar3d.png new file mode 100644 index 00000000..16deedd0 Binary files /dev/null and b/examples/output/png/bar3d.png differ diff --git a/examples/output/png/clipping_clip.png b/examples/output/png/clipping_clip.png new file mode 100644 index 00000000..45bb9b08 Binary files /dev/null and b/examples/output/png/clipping_clip.png differ diff --git a/examples/output/png/clipping_hide.png b/examples/output/png/clipping_hide.png new file mode 100644 index 00000000..76572f31 Binary files /dev/null and b/examples/output/png/clipping_hide.png differ diff --git a/examples/output/png/clipping_none.png b/examples/output/png/clipping_none.png new file mode 100644 index 00000000..d854bc5d Binary files /dev/null and b/examples/output/png/clipping_none.png differ diff --git a/examples/output/png/contour_projection.png b/examples/output/png/contour_projection.png new file mode 100644 index 00000000..02fc4720 Binary files /dev/null and b/examples/output/png/contour_projection.png differ diff --git a/examples/output/png/line_scatter_text.png b/examples/output/png/line_scatter_text.png new file mode 100644 index 00000000..2eeaef17 Binary files /dev/null and b/examples/output/png/line_scatter_text.png differ diff --git a/examples/output/png/log_clipping.png b/examples/output/png/log_clipping.png new file mode 100644 index 00000000..8cf7eed9 Binary files /dev/null and b/examples/output/png/log_clipping.png differ diff --git a/examples/output/png/quiver3d.png b/examples/output/png/quiver3d.png new file mode 100644 index 00000000..10c9475b Binary files /dev/null and b/examples/output/png/quiver3d.png differ diff --git a/examples/output/png/surface_wireframe.png b/examples/output/png/surface_wireframe.png new file mode 100644 index 00000000..4ef0bb42 Binary files /dev/null and b/examples/output/png/surface_wireframe.png differ diff --git a/examples/plot_3d_gallery.py b/examples/plot_3d_gallery.py new file mode 100644 index 00000000..3e6c8664 --- /dev/null +++ b/examples/plot_3d_gallery.py @@ -0,0 +1,292 @@ +"""Generate the 3D example gallery used by the README.""" + +from __future__ import annotations + +import shutil +import subprocess +from pathlib import Path +from typing import TYPE_CHECKING, Literal, cast + +import matplotlib as mpl +import matplotlib.pyplot as plt +import numpy as np + +import matplot2tikz + +if TYPE_CHECKING: + from collections.abc import Callable + + from matplotlib.figure import Figure + from mpl_toolkits.mplot3d import Axes3D + +mpl.use("Agg") + +ROOT = Path(__file__).resolve().parents[1] +EXAMPLE_DIR = ROOT / "examples" +TEX_DIR = EXAMPLE_DIR / "output" / "tex" +PNG_DIR = EXAMPLE_DIR / "output" / "png" +BUILD_DIR = EXAMPLE_DIR / "output" / "build" +Clip3DMode = Literal["none", "hide", "clip"] + + +def line_scatter_text() -> Figure: + """Return a 3D line, scatter, legend, and text example.""" + fig = plt.figure(figsize=(4.2, 3.2)) + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + theta = np.linspace(0.0, 2.6 * np.pi, 22) + radius = 0.18 + 0.035 * theta + ax.plot( + radius * np.cos(theta), + radius * np.sin(theta), + 0.12 * theta, + color="tab:red", + marker="o", + label="spiral", + ) + x = np.linspace(-0.8, 0.8, 8) + ax.plot(x, 0.2 * np.sin(4 * x), 0.25 + 0.15 * np.cos(3 * x), "--", label="ridge") + xs, ys = np.meshgrid(np.linspace(-0.5, 0.5, 4), np.linspace(-0.4, 0.4, 3)) + zs = 0.2 + xs**2 + 0.35 * ys**2 + ax.scatter(xs.ravel(), ys.ravel(), zs.ravel(), c=zs.ravel(), s=32, cmap="viridis") + ax.text(0.0, -0.55, 0.78, "3D text") + ax.set_xlabel("X") + ax.set_ylabel("Y") + ax.set_zlabel("Z") + ax.legend(loc="upper left") + ax.view_init(elev=28.0, azim=38.0) + return fig + + +def surface_wireframe() -> Figure: + """Return a surface with an overlaid wireframe.""" + fig = plt.figure(figsize=(4.2, 3.2)) + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + x = np.linspace(-2.5, 2.5, 11) + y = np.linspace(-2.5, 2.5, 11) + xx, yy = np.meshgrid(x, y) + zz = np.sin(np.hypot(xx, yy)) + ax.plot_surface(xx, yy, zz, cmap="viridis", edgecolor="black", linewidth=0.2, alpha=0.9) + ax.plot_wireframe(xx, yy, zz + 0.75, color="black", linewidth=0.45, rstride=2, cstride=2) + ax.set_xlabel("X") + ax.set_ylabel("Y") + ax.set_zlabel("Z") + ax.view_init(elev=24.0, azim=-55.0) + return fig + + +def contour_projection() -> Figure: + """Return 3D contour lines and filled contour patches.""" + fig = plt.figure(figsize=(4.2, 3.2)) + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + x = np.linspace(-2.0, 2.0, 10) + y = np.linspace(-2.0, 2.0, 10) + xx, yy = np.meshgrid(x, y) + zz = np.cos(xx) * np.sin(yy) + ax.contour( + xx, + yy, + zz, + levels=[-0.6, -0.2, 0.2, 0.6], + colors=["navy", "teal", "orange", "crimson"], + ) + ax.contourf(xx, yy, zz, zdir="z", offset=-1.1, levels=5, cmap="cividis", alpha=0.65) + ax.set_zlim(-1.1, 1.0) + ax.set_xlabel("X") + ax.set_ylabel("Y") + ax.set_zlabel("Z") + ax.view_init(elev=25.0, azim=105.0) + return fig + + +def bars() -> Figure: + """Return a 3D bar chart with custom tick labels.""" + fig = plt.figure(figsize=(4.2, 3.2)) + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + xpos, ypos = np.meshgrid([0.0, 1.0, 2.0], [0.0, 1.0]) + heights = np.array([0.5, 1.2, 0.8, 1.4, 0.7, 1.6]) + colors = plt.get_cmap("cividis")((heights - heights.min()) / np.ptp(heights)) + ax.bar3d( + xpos.ravel(), + ypos.ravel(), + np.zeros(6), + 0.55, + 0.55, + heights, + color=colors, + edgecolor="black", + shade=False, + ) + ax.set_xlabel("X") + ax.set_ylabel("Y") + ax.set_zlabel("Z") + ax.set_xticks([0.25, 1.25, 2.25], ["low", "mid", "high"]) + ax.set_yticks([0.25, 1.25], ["front", "back"]) + ax.view_init(elev=26.0, azim=35.0) + return fig + + +def quiver() -> Figure: + """Return a semantic 3D quiver example.""" + fig = plt.figure(figsize=(4.2, 3.2)) + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + x = np.array([0.0, 0.9, 0.0, 0.9]) + y = np.array([0.0, 0.0, 0.8, 0.8]) + z = np.array([0.0, 0.2, 0.1, 0.3]) + u = np.array([0.45, -0.2, 0.25, -0.3]) + v = np.array([0.15, 0.35, -0.25, -0.2]) + w = np.array([0.35, 0.25, 0.45, 0.2]) + ax.quiver(x, y, z, u, v, w, length=0.8, arrow_length_ratio=0.25, color="tab:green") + ax.set_xlabel("X") + ax.set_ylabel("Y") + ax.set_zlabel("Z") + ax.view_init(elev=30.0, azim=-45.0) + return fig + + +def clipping_surface() -> Figure: + """Return a surface and wireframe scene that crosses the 3D axis box.""" + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + x = np.arange(-5, 5, 0.5) + y = np.arange(-5, 5, 0.5) + xx, yy = np.meshgrid(x, y) + zz = np.sin(np.sqrt(xx**2 + yy**2)) + ax.plot_surface( + xx, + yy, + zz, + cmap=plt.get_cmap("viridis"), + edgecolor="black", + linewidth=0.25, + alpha=0.88, + ) + ax.plot_wireframe( + xx, + yy, + zz + 0.75, + color="black", + linewidth=0.45, + rstride=2, + cstride=2, + ) + ax.contour( + xx, + yy, + zz, + levels=[-0.2, 0.0, 0.2], + zdir="z", + offset=-0.85, + cmap="viridis", + linewidths=0.6, + ) + ax.set_xlabel("X") + ax.set_ylabel("Y") + ax.set_zlabel("Z") + ax.set_xlim(-4, 4) + ax.set_ylim(-4, 4) + ax.set_zlim(-1, 0.5) + ax.view_init(elev=24.0, azim=-55.0) + + return fig + + +def clipping_surface_none() -> Figure: + """Return the clipping comparison scene without export clipping.""" + return clipping_surface() + + +def clipping_surface_hide() -> Figure: + """Return the clipping comparison scene for hide clipping.""" + return clipping_surface() + + +def clipping_surface_clip() -> Figure: + """Return the clipping comparison scene for geometric clipping.""" + return clipping_surface() + + +def log_clipping() -> Figure: + """Return a log-scaled 3D line clipped in axis-scale coordinates.""" + fig = plt.figure(figsize=(4.2, 3.2)) + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + ax.plot([0.1, 10.0], [0.0, 2.0], [1.0, 1.0], marker="o", color="tab:blue") + ax.set_xlim(1.0, 10.0) + ax.set_xscale("log") + ax.set_ylim(0.0, 2.0) + ax.set_zlim(0.0, 2.0) + ax.set_xlabel(r"$\log_{10} \mathrm{X}$") + ax.set_ylabel("Y") + ax.set_zlabel("Z") + ax.view_init(elev=24.0, azim=-35.0) + return fig + + +EXAMPLES: tuple[tuple[str, str, Callable[[], Figure], Clip3DMode], ...] = ( + ("line_scatter_text", "Line, scatter, text", line_scatter_text, "none"), + ("surface_wireframe", "Surface and wireframe", surface_wireframe, "none"), + ("contour_projection", "Contours and filled contours", contour_projection, "none"), + ("bar3d", "3D bars", bars, "none"), + ("quiver3d", "Semantic quiver", quiver, "none"), + ("log_clipping", "Log-scale clipping", log_clipping, "clip"), + ("clipping_none", "No clipping", clipping_surface_none, "none"), + ("clipping_hide", "Hide outside", clipping_surface_hide, "hide"), + ("clipping_clip", "Clip to limits", clipping_surface_clip, "clip"), +) + + +def main() -> None: + """Generate TeX examples and PNG previews when the local TeX tools exist.""" + TEX_DIR.mkdir(parents=True, exist_ok=True) + PNG_DIR.mkdir(parents=True, exist_ok=True) + BUILD_DIR.mkdir(parents=True, exist_ok=True) + + expected_names = {name for name, _title, _plot, _clip_3d in EXAMPLES} + for output_dir, suffix in ((TEX_DIR, ".tex"), (PNG_DIR, ".png")): + for path in output_dir.glob(f"*{suffix}"): + if path.stem not in expected_names: + path.unlink() + + pdflatex = shutil.which("pdflatex") + pdftoppm = shutil.which("pdftoppm") + for name, _title, plot, clip_3d in EXAMPLES: + fig = plot() + tex_path = TEX_DIR / f"{name}.tex" + fig.savefig(BUILD_DIR / f"{name}_reference.png", dpi=fig.dpi) + matplot2tikz.save( + tex_path, + figure=fig, + standalone=True, + include_disclaimer=False, + float_format=".8g", + clip_3d=clip_3d, + ) + plt.close(fig) + + if pdflatex is None or pdftoppm is None: + continue + + subprocess.run( # noqa: S603 + [ + pdflatex, + "-interaction=nonstopmode", + "-halt-on-error", + "-output-directory", + str(BUILD_DIR), + str(tex_path), + ], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT, + ) + pdf_path = BUILD_DIR / f"{name}.pdf" + png_stem = PNG_DIR / name + subprocess.run( # noqa: S603 + [pdftoppm, "-png", "-singlefile", "-r", "170", str(pdf_path), str(png_stem)], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT, + ) + + +if __name__ == "__main__": + main() diff --git a/src/matplot2tikz/_axes.py b/src/matplot2tikz/_axes.py index 72d976b3..d592208e 100644 --- a/src/matplot2tikz/_axes.py +++ b/src/matplot2tikz/_axes.py @@ -1,13 +1,14 @@ from __future__ import annotations import re -from collections.abc import Iterable, Sized -from typing import TYPE_CHECKING +from collections.abc import Iterable, Sequence, Sized +from typing import TYPE_CHECKING, cast import matplotlib.pyplot as plt import numpy as np from matplotlib.axes import Subplot from matplotlib.colors import Colormap, LinearSegmentedColormap, ListedColormap +from mpl_toolkits.mplot3d import Axes3D from . import _color from ._util import _common_texification @@ -15,6 +16,8 @@ if TYPE_CHECKING: from matplotlib.axes import Axes from matplotlib.colorbar import Colorbar + from matplotlib.lines import Line2D + from matplotlib.text import Text from ._tikzdata import TikzData @@ -32,6 +35,8 @@ def __init__(self, data: TikzData, obj: Axes) -> None: if self.is_colorbar: return + self.is_3d = isinstance(obj, Axes3D) + # instantiation self.nsubplots = 1 self.subplot_index = 0 @@ -45,7 +50,8 @@ def __init__(self, data: TikzData, obj: Axes) -> None: self._set_hide_axis() self._set_plot_title() self._set_axis_titles() - xlim, ylim = self._set_axis_limits() + xlim, ylim, _ = self._set_axis_limits() + self._set_view() self._set_axis_scaling() self._set_axis_on_top() self._set_axis_dimensions(self._get_aspect_ratio(), xlim, ylim) @@ -59,7 +65,9 @@ def __init__(self, data: TikzData, obj: Axes) -> None: def _set_hide_axis(self) -> None: # check if axes need to be displayed at all - if not self.obj.axison: + if self.is_3d and not self.obj._axis3don: # type: ignore[attr-defined] # noqa: SLF001 + self.data.current_axis_options.add("hide axis") + elif not self.is_3d and not self.obj.axison: self.data.current_axis_options.add("hide x axis") self.data.current_axis_options.add("hide y axis") @@ -87,7 +95,7 @@ def _set_axis_titles(self) -> None: self.data.current_axis_options.add(f"xlabel={{{xlabel}}}") xrotation = self.obj.xaxis.get_label().get_rotation() - if xrotation != 0: + if not self.is_3d and xrotation != 0: self.data.current_axis_options.add(f"xlabel style={{rotate={xrotation - 90}}}") ylabel = self.obj.get_ylabel() @@ -102,10 +110,22 @@ def _set_axis_titles(self) -> None: self.data.current_axis_options.add(f"ylabel={{{ylabel}}}") yrotation = self.obj.yaxis.get_label().get_rotation() - if yrotation != 90: # noqa: PLR2004 + if not self.is_3d and yrotation != 90: # noqa: PLR2004 self.data.current_axis_options.add(f"ylabel style={{rotate={yrotation - 90}}}") - def _set_axis_limits(self) -> tuple[list[float], list[float]]: + if self.is_3d: + zlabel = self.obj.get_zlabel() # type: ignore[attr-defined] + if zlabel: + zlabel = _common_texification(zlabel) + + labelcolor = self.obj.zaxis.label.get_color() # type: ignore[attr-defined] + if labelcolor != "black": + col, _ = _color.mpl_color2xcolor(self.data, labelcolor) + self.data.current_axis_options.add(f"zlabel=\\textcolor{{{col}}}{{{zlabel}}}") + else: + self.data.current_axis_options.add(f"zlabel={{{zlabel}}}") + + def _set_axis_limits(self) -> tuple[list[float], list[float], list[float] | None]: ff = self.data.float_format xlim = list(self.obj.get_xlim()) xlim0, xlim1 = sorted(xlim) @@ -119,7 +139,24 @@ def _set_axis_limits(self) -> tuple[list[float], list[float]]: self.data.current_axis_options.add("x dir=reverse") if ylim != sorted(ylim): self.data.current_axis_options.add("y dir=reverse") - return xlim, ylim + # For 3D axes, also set zlim. + if self.is_3d: + zlim = list(self.obj.get_zlim()) # type: ignore[attr-defined] + zlim0, zlim1 = sorted(zlim) + self.data.current_axis_options.add(f"zmin={zlim0:{ff}}, zmax={zlim1:{ff}}") + if zlim != sorted(zlim): + self.data.current_axis_options.add("z dir=reverse") + return xlim, ylim, zlim + return xlim, ylim, None + + def _set_view(self) -> None: + if not self.is_3d: + return + ff = self.data.float_format + # PGFPlots' 3D azimuth is rotated 90 degrees relative to mplot3d. + azim = self.obj.azim + 90 # type: ignore[attr-defined] + elev = self.obj.elev # type: ignore[attr-defined] + self.data.current_axis_options.add("view={" + f"{azim:{ff}}" + "}{" + f"{elev:{ff}}" + "}") def _set_axis_scaling(self) -> None: if self.obj.get_xscale() == "log": @@ -132,6 +169,11 @@ def _set_axis_scaling(self) -> None: self.data.current_axis_options.add( f"log basis y={{{_try_f2i(self.obj.yaxis._scale.base)}}}" # type: ignore[attr-defined] # noqa: SLF001 ) + if self.is_3d and self.obj.get_zscale() == "log": # type: ignore[attr-defined] + self.data.current_axis_options.add("zmode=log") + self.data.current_axis_options.add( + f"log basis z={{{_try_f2i(self.obj.zaxis._scale.base)}}}" # type: ignore[attr-defined] # noqa: SLF001 + ) def _set_axis_on_top(self) -> None: # Possible values for get_axisbelow(): @@ -197,6 +239,8 @@ def _set_axis_positions(self) -> None: def _set_ticks(self) -> None: self._get_ticks() self._get_tick_colors() + if self.is_3d: + return self._get_tick_direction() self._set_tick_rotation() self._set_tick_positions() @@ -205,6 +249,35 @@ def _set_grid(self) -> None: # Don't use get_{x,y}gridlines for gridlines; see discussion on # Coordinate of # the lines are entirely meaningless, but styles (colors,...) are respected. + ( + has_major_xgrid, + has_minor_xgrid, + has_major_ygrid, + has_minor_ygrid, + has_major_zgrid, + has_minor_zgrid, + ) = self._get_grid_visibility() + + self._set_grid_options("x", (has_major_xgrid, has_minor_xgrid), self.obj.get_xgridlines()) + self._set_grid_options("y", (has_major_ygrid, has_minor_ygrid), self.obj.get_ygridlines()) + if self.is_3d: + self._set_grid_options( + "z", + (has_major_zgrid, has_minor_zgrid), + self.obj.get_zgridlines(), # type: ignore[attr-defined] + ) + + def _get_grid_visibility(self) -> tuple[bool, bool, bool, bool, bool, bool]: + if self.is_3d: + has_major_grid = bool(self.obj._draw_grid) # type: ignore[attr-defined] # noqa: SLF001 + return ( + has_major_grid, + False, + has_major_grid, + False, + has_major_grid, + False, + ) try: # mpl 3.3.3+ @@ -219,29 +292,29 @@ def _set_grid(self) -> None: has_major_ygrid = self.obj.yaxis._gridOnMajor # type: ignore[attr-defined] # noqa: SLF001 has_minor_ygrid = self.obj.yaxis._gridOnMinor # type: ignore[attr-defined] # noqa: SLF001 - if has_major_xgrid: - self.data.current_axis_options.add("xmajorgrids") - if has_minor_xgrid: - self.data.current_axis_options.add("xminorgrids") - - xlines = self.obj.get_xgridlines() - if xlines: - xgridcolor = xlines[0].get_color() - col, _ = _color.mpl_color2xcolor(self.data, xgridcolor) - if col != "black": - self.data.current_axis_options.add(f"x grid style={{{col}}}") - - if has_major_ygrid: - self.data.current_axis_options.add("ymajorgrids") - if has_minor_ygrid: - self.data.current_axis_options.add("yminorgrids") - - ylines = self.obj.get_ygridlines() - if ylines: - ygridcolor = ylines[0].get_color() - col, _ = _color.mpl_color2xcolor(self.data, ygridcolor) - if col != "black": - self.data.current_axis_options.add(f"y grid style={{{col}}}") + return ( + has_major_xgrid, + has_minor_xgrid, + has_major_ygrid, + has_minor_ygrid, + False, + False, + ) + + def _set_grid_options( + self, axis_name: str, grid_state: tuple[bool, bool], gridlines: Sequence[Line2D] + ) -> None: + has_major_grid, has_minor_grid = grid_state + if has_major_grid: + self.data.current_axis_options.add(f"{axis_name}majorgrids") + if has_minor_grid: + self.data.current_axis_options.add(f"{axis_name}minorgrids") + if not gridlines: + return + gridcolor = gridlines[0].get_color() + col, _ = _color.mpl_color2xcolor(self.data, gridcolor) + if col != "black": + self.data.current_axis_options.add(f"{axis_name} grid style={{{col}}}") def _set_axis_line_styles(self) -> None: # Assume that the bottom edge color is the color of the entire box. @@ -362,47 +435,40 @@ def get_end_code(self) -> str: return "" def _get_ticks(self) -> None: - self.data.current_axis_options.update( - _get_ticks(self.data, "x", self.obj.get_xticks(), self.obj.get_xticklabels()) - ) - self.data.current_axis_options.update( - _get_ticks(self.data, "y", self.obj.get_yticks(), self.obj.get_yticklabels()) - ) - self.data.current_axis_options.update( - _get_ticks( - self.data, - "minor x", - self.obj.get_xticks(minor=True), - self.obj.get_xticklabels(minor=True), - ) - ) - self.data.current_axis_options.update( - _get_ticks( - self.data, - "minor y", - self.obj.get_yticks(minor=True), - self.obj.get_yticklabels(minor=True), - ) - ) + tick_iterators = [ + ("x", self.obj.get_xticks(), self.obj.get_xticklabels()), + ("y", self.obj.get_yticks(), self.obj.get_yticklabels()), + ("minor x", self.obj.get_xticks(minor=True), self.obj.get_xticklabels(minor=True)), + ("minor y", self.obj.get_yticks(minor=True), self.obj.get_yticklabels(minor=True)), + ] + + if self.is_3d: + axes3d = cast("Axes3D", self.obj) + tick_iterators += [ + ("z", axes3d.get_zticks(), axes3d.get_zticklabels()), + ("minor z", axes3d.get_zticks(minor=True), axes3d.get_zticklabels(minor=True)), + ] + + for axis_name, ticks, ticklabels in tick_iterators: + self._add_tick_options(axis_name, ticks, ticklabels) + + def _add_tick_options( + self, axis_name: str, ticks: Sequence[float] | np.ndarray, ticklabels: Sequence[Text] + ) -> None: + self.data.current_axis_options.update(_get_ticks(self.data, axis_name, ticks, ticklabels)) def _get_tick_colors(self) -> None: - try: - l0 = self.obj.get_xticklines()[0] - except IndexError: - pass - else: - c0 = l0.get_color() - xtickcolor, _ = _color.mpl_color2xcolor(self.data, c0) - self.data.current_axis_options.add(f"xtick style={{color={xtickcolor}}}") + self._add_tick_color_option("x", self.obj.get_xticklines()) + self._add_tick_color_option("y", self.obj.get_yticklines()) + if self.is_3d: + self._add_tick_color_option("z", cast("Axes3D", self.obj).get_zticklines()) - try: - l0 = self.obj.get_yticklines()[0] - except IndexError: - pass - else: - c0 = l0.get_color() - ytickcolor, _ = _color.mpl_color2xcolor(self.data, c0) - self.data.current_axis_options.add(f"ytick style={{color={ytickcolor}}}") + def _add_tick_color_option(self, axis_name: str, ticklines: Sequence[Line2D]) -> None: + if not ticklines: + return + first_tickline = ticklines[0] + tickcolor, _ = _color.mpl_color2xcolor(self.data, first_tickline.get_color()) + self.data.current_axis_options.add(f"{axis_name}tick style={{color={tickcolor}}}") def _get_tick_direction(self) -> None: # For new matplotlib versions, we could replace the direction getter by @@ -564,7 +630,12 @@ def _get_tick_position(obj: Axes, x_or_y: str) -> tuple[str | None, str | None]: return position_string, major_ticks_position -def _get_ticks(data: TikzData, xy: str, ticks: list | np.ndarray, ticklabels: list) -> list[str]: +def _get_ticks( + data: TikzData, + xy: str, + ticks: Sequence[float] | np.ndarray, + ticklabels: Sequence[Text], +) -> list[str]: """Gets a {'x','y'}, a number of ticks and ticks labels. Returns the necessary axis options for the given configuration. @@ -603,7 +674,7 @@ def _get_ticks(data: TikzData, xy: str, ticks: list | np.ndarray, ticklabels: li return axis_options -def _is_label_required(ticks: list | np.ndarray, ticklabels: list) -> bool: +def _is_label_required(ticks: Sequence[float] | np.ndarray, ticklabels: Sequence[Text]) -> bool: """Check if the label is necessary. If one of the labels is, then all of them must appear in the TikZ plot. @@ -631,7 +702,7 @@ def _is_label_required(ticks: list | np.ndarray, ticklabels: list) -> bool: return False -def _get_pgfplots_ticklabels(ticklabels: list) -> list[str]: +def _get_pgfplots_ticklabels(ticklabels: Sequence[Text]) -> list[str]: pgfplots_ticklabels = [] for ticklabel in ticklabels: label = ticklabel.get_text() diff --git a/src/matplot2tikz/_cleanfigure.py b/src/matplot2tikz/_cleanfigure.py index 4f578ebd..c3f8291f 100644 --- a/src/matplot2tikz/_cleanfigure.py +++ b/src/matplot2tikz/_cleanfigure.py @@ -251,7 +251,7 @@ def _clean_collections( cfd.has_lines = False data = _simplify_line(cfd) data = _limit_precision(cfd.axes, data, cfd.scale_precision) - collection.set_offsets(data) + _update_collection_data(collection, data) def _is_step(linehandle: Line2D | art3d.Line3D) -> bool: @@ -343,6 +343,17 @@ def _update_line_data(linehandle: Line2D | art3d.Line3D, data: np.ndarray) -> No linehandle.set_ydata(y_data) +def _update_collection_data( + collection: PathCollection | art3d.Path3DCollection, data: np.ndarray +) -> None: + if isinstance(collection, art3d.Path3DCollection): + x_data, y_data, z_data = _split_data_3d(data) + collection._offsets3d = (x_data, y_data, z_data) # noqa: SLF001 + collection.set_offsets(_stack_data_2d(x_data, y_data)) + else: + collection.set_offsets(data) + + def _split_data_2d(data: np.ndarray) -> tuple[np.ndarray, np.ndarray]: """Convert data to 2 different arrays.""" x_data, y_data = np.split(data, 2, axis=1) diff --git a/src/matplot2tikz/_clip3d.py b/src/matplot2tikz/_clip3d.py new file mode 100644 index 00000000..00796105 --- /dev/null +++ b/src/matplot2tikz/_clip3d.py @@ -0,0 +1,247 @@ +"""Clip exported 3D artists to Matplotlib axis limits.""" + +from __future__ import annotations + +from dataclasses import dataclass +from itertools import pairwise +from typing import TYPE_CHECKING, Literal + +import numpy as np + +if TYPE_CHECKING: + from matplotlib.axes import Axes + from matplotlib.transforms import Transform + +Clip3DMode = Literal["none", "hide", "clip"] +POLYGON_RANK = 2 + + +@dataclass +class ClipBox3D: + xlim: tuple[float, float] + ylim: tuple[float, float] + zlim: tuple[float, float] + xtransform: Transform + ytransform: Transform + ztransform: Transform + + +def clip_box_from_axes(axes: Axes) -> ClipBox3D: + """Return the 3D clipping box in transformed axis coordinates.""" + xlim = _transformed_limits(axes.xaxis.get_transform(), axes.get_xlim()) + ylim = _transformed_limits(axes.yaxis.get_transform(), axes.get_ylim()) + zlim = _transformed_limits(axes.zaxis.get_transform(), axes.get_zlim()) # type: ignore[attr-defined] + return ClipBox3D( + xlim=xlim, + ylim=ylim, + zlim=zlim, + xtransform=axes.xaxis.get_transform(), + ytransform=axes.yaxis.get_transform(), + ztransform=axes.zaxis.get_transform(), # type: ignore[attr-defined] + ) + + +def points_inside(points: np.ndarray, box: ClipBox3D) -> np.ndarray: + """Return a mask for points inside the 3D clipping box.""" + transformed = transform_points(points, box) + return _points_inside_transformed(transformed, box) + + +def clip_line_to_box(points: np.ndarray, box: ClipBox3D, mode: Clip3DMode) -> list[np.ndarray]: + """Return line segments hidden or clipped to the 3D clipping box. + + Line clipping uses a Liang-Barsky style parametric interval against the six + axis-aligned box planes after applying the Matplotlib axis transforms. + """ + transformed = transform_points(points, box) + segments = _line_segments(transformed, box, mode) + return [inverse_transform_points(segment, box) for segment in segments] + + +def clip_polygon_to_box(poly: np.ndarray, box: ClipBox3D, mode: Clip3DMode) -> np.ndarray: + """Return a polygon hidden or clipped to the 3D clipping box. + + Polygon clipping uses Sutherland-Hodgman clipping against each box plane + after applying the Matplotlib axis transforms. + """ + transformed = transform_points(poly, box) + if mode == "hide": + if np.all(_points_inside_transformed(transformed, box)): + return poly + return np.empty((0, 3), dtype=float) + if mode == "clip": + clipped = _clip_polygon_transformed(transformed, box) + return inverse_transform_points(clipped, box) if len(clipped) else clipped + return poly + + +def transform_points(points: np.ndarray, box: ClipBox3D) -> np.ndarray: + """Transform data coordinates into axis-scale coordinates for clipping.""" + points = np.asarray(points, dtype=float) + return np.column_stack( + [ + box.xtransform.transform(points[:, 0]), + box.ytransform.transform(points[:, 1]), + box.ztransform.transform(points[:, 2]), + ] + ) + + +def inverse_transform_points(points: np.ndarray, box: ClipBox3D) -> np.ndarray: + """Transform clipped axis-scale coordinates back into data coordinates.""" + points = np.asarray(points, dtype=float) + return np.column_stack( + [ + box.xtransform.inverted().transform(points[:, 0]), + box.ytransform.inverted().transform(points[:, 1]), + box.ztransform.inverted().transform(points[:, 2]), + ] + ) + + +def _transformed_limits(transform: Transform, limits: tuple[float, float]) -> tuple[float, float]: + transformed = np.asarray(transform.transform(limits), dtype=float) + finite = transformed[np.isfinite(transformed)] + if len(finite) != 2: # noqa: PLR2004 + return (np.nan, np.nan) + return (float(np.min(finite)), float(np.max(finite))) + + +def _points_inside_transformed(points: np.ndarray, box: ClipBox3D) -> np.ndarray: + return np.logical_and.reduce( + ( + np.isfinite(points).all(axis=1), + points[:, 0] >= box.xlim[0], + points[:, 0] <= box.xlim[1], + points[:, 1] >= box.ylim[0], + points[:, 1] <= box.ylim[1], + points[:, 2] >= box.zlim[0], + points[:, 2] <= box.zlim[1], + ) + ) + + +def _line_segments(points: np.ndarray, box: ClipBox3D, mode: Clip3DMode) -> list[np.ndarray]: + if len(points) < 2: # noqa: PLR2004 + return [] + + inside = _points_inside_transformed(points, box) + segments: list[np.ndarray] = [] + previous_edge_visible = False + for i, (p0, p1) in enumerate(pairwise(points)): + clipped = np.empty((0, 3), dtype=float) + if mode == "hide": + if inside[i] and inside[i + 1]: + clipped = np.asarray([p0, p1], dtype=float) + elif mode == "clip": + clipped = _clip_line_segment_transformed(p0, p1, box) + if len(clipped): + _append_line_segment(segments, clipped, merge=previous_edge_visible) + previous_edge_visible = True + else: + previous_edge_visible = False + return segments + + +def _append_line_segment(segments: list[np.ndarray], segment: np.ndarray, *, merge: bool) -> None: + if len(segment) < 2: # noqa: PLR2004 + return + if merge and segments and np.allclose(segments[-1][-1], segment[0]): + segments[-1] = np.vstack([segments[-1], segment[1:]]) + return + segments.append(segment) + + +def _clip_line_segment_transformed(p0: np.ndarray, p1: np.ndarray, box: ClipBox3D) -> np.ndarray: + if not np.isfinite(p0).all() or not np.isfinite(p1).all(): + return np.empty((0, 3), dtype=float) + + direction = p1 - p0 + t0 = 0.0 + t1 = 1.0 + for axis, (lower, upper) in enumerate((box.xlim, box.ylim, box.zlim)): + if not np.isfinite([lower, upper]).all(): + return np.empty((0, 3), dtype=float) + if direction[axis] == 0: + if p0[axis] < lower or p0[axis] > upper: + return np.empty((0, 3), dtype=float) + continue + + t_lower = (lower - p0[axis]) / direction[axis] + t_upper = (upper - p0[axis]) / direction[axis] + t_enter = min(t_lower, t_upper) + t_exit = max(t_lower, t_upper) + t0 = max(t0, t_enter) + t1 = min(t1, t_exit) + if t0 > t1: + return np.empty((0, 3), dtype=float) + + start = p0 + t0 * direction + end = p0 + t1 * direction + if np.allclose(start, end): + return np.empty((0, 3), dtype=float) + return np.asarray([start, end], dtype=float) + + +def _clip_polygon_transformed(poly: np.ndarray, box: ClipBox3D) -> np.ndarray: + if _is_degenerate_polygon(poly): + return np.empty((0, 3), dtype=float) + + for axis, value, keep_greater in ( + (0, box.xlim[0], True), + (0, box.xlim[1], False), + (1, box.ylim[0], True), + (1, box.ylim[1], False), + (2, box.zlim[0], True), + (2, box.zlim[1], False), + ): + if not np.isfinite(value): + return np.empty((0, 3), dtype=float) + poly = _clip_polygon_against_plane(poly, axis, value, keep_greater=keep_greater) + if len(poly) < 3: # noqa: PLR2004 + return np.empty((0, 3), dtype=float) + + if _is_degenerate_polygon(poly): + return np.empty((0, 3), dtype=float) + return poly + + +def _clip_polygon_against_plane( + poly: np.ndarray, axis: int, value: float, *, keep_greater: bool +) -> np.ndarray: + clipped = [] + previous = poly[-1] + previous_inside = _inside_plane(previous, axis, value, keep_greater=keep_greater) + + for current in poly: + current_inside = _inside_plane(current, axis, value, keep_greater=keep_greater) + if current_inside != previous_inside: + denominator = current[axis] - previous[axis] + if not np.isclose(denominator, 0.0): + t = np.clip((value - previous[axis]) / denominator, 0.0, 1.0) + clipped.append(previous + t * (current - previous)) + if current_inside: + clipped.append(current) + + previous = current + previous_inside = current_inside + + return np.asarray(clipped, dtype=float) if clipped else np.empty((0, 3), dtype=float) + + +def _inside_plane(point: np.ndarray, axis: int, value: float, *, keep_greater: bool) -> bool: + return bool(point[axis] >= value if keep_greater else point[axis] <= value) + + +def _is_degenerate_polygon(poly: np.ndarray, tol: float = 1e-12) -> bool: + poly = np.asarray(poly, dtype=float) + if poly.ndim != 2 or poly.shape[1] != 3 or len(poly) < 3: # noqa: PLR2004 + return True + if not np.isfinite(poly).all(): + return True + + centered = poly - poly.mean(axis=0) + scale = np.linalg.norm(centered, axis=1).max(initial=0) + if scale == 0: + return True + return bool(np.linalg.matrix_rank(centered, tol=tol * scale) < POLYGON_RANK) diff --git a/src/matplot2tikz/_line2d.py b/src/matplot2tikz/_line2d.py index 7f675451..39624279 100644 --- a/src/matplot2tikz/_line2d.py +++ b/src/matplot2tikz/_line2d.py @@ -316,7 +316,7 @@ def _table(data: TikzData, obj: Line2D) -> list[str]: opts_str = ("[" + ",".join(opts) + "] ") if len(opts) > 0 else "" posix_filepath = rel_filepath.as_posix() - content.append(f"table {{{opts_str}}}{{{posix_filepath}}};\n") + content.append(f"table {opts_str}{{{posix_filepath}}};\n") else: if len(opts) > 0: opts_str = ",".join(opts) diff --git a/src/matplot2tikz/_mplot3d.py b/src/matplot2tikz/_mplot3d.py new file mode 100644 index 00000000..d67ffbf6 --- /dev/null +++ b/src/matplot2tikz/_mplot3d.py @@ -0,0 +1,1035 @@ +from __future__ import annotations + +from collections.abc import Iterable, Sequence +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypeVar, cast + +import numpy as np +from matplotlib.path import Path +from mpl_toolkits.mplot3d.art3d import ( + Line3D, + Line3DCollection, + Path3DCollection, + Poly3DCollection, + Text3D, +) + +from . import _files, _line2d +from . import _path as mypath +from ._axes import _mpl_cmap2pgf_cmap +from ._clip3d import ( + ClipBox3D, + clip_box_from_axes, + clip_line_to_box, + clip_polygon_to_box, + points_inside, +) +from ._util import get_legend_text, has_legend + +if TYPE_CHECKING: + from matplotlib.collections import Collection, LineCollection, PathCollection + from matplotlib.lines import Line2D + from matplotlib.text import Text + + from ._tikzdata import TikzData + +MIN_POLYGON_VERTICES = 3 +RGBA_LENGTH = 4 +HashableColor = tuple[float, ...] +LineStyle = str | tuple[float, Sequence[float] | None] | None +T = TypeVar("T") + + +@dataclass +class Poly3DStyle: + facecolor: np.ndarray | None + edgecolor: np.ndarray | None + linewidth: float | None + linestyle: LineStyle + + +@dataclass +class Poly3DStyleArrays: + facecolors: np.ndarray | None + edgecolors: np.ndarray + linewidths: Sequence[float | None] | np.ndarray + linestyles: Sequence[LineStyle] + + +@dataclass +class Poly3DColorGroup: + segments: list[np.ndarray] + colors: list[np.ndarray | None] + style_options: list[str] + vertex_count: int + + +@dataclass +class Quiver3DData: + coordinates: np.ndarray + options: list[str] + + +def is_quiver3d_collection(obj: Collection) -> bool: + # Since a quiver3d collection is represented as a Line3DCollection with a + # specific structure of segments and uniform style, both must be checked. + if not isinstance(obj, Line3DCollection): + return False + style_arrays = _poly3d_style_arrays(obj) + if not _quiver3d_has_uniform_style(style_arrays): + return False + return _quiver3d_coordinates(get_segments3d(obj)) is not None + + +def is_contour3d_collection(obj: Collection) -> bool: + return hasattr(obj, "_3dverts_codes") + + +def get_text3d_position(obj: Text) -> tuple[float, float, float]: + if not isinstance(obj, Text3D): + msg = f"Expected Text3D, got {type(obj)}." + raise TypeError(msg) + return obj.get_position_3d() + + +def get_line3d_data(obj: Line2D) -> np.ndarray: + if not isinstance(obj, Line3D): + msg = f"Expected Line3D, got {type(obj)}." + raise TypeError(msg) + xdata, ydata, zdata = obj.get_data_3d() + return np.column_stack([xdata, ydata, zdata]) + + +def get_offsets3d(obj: PathCollection) -> np.ndarray: + if not isinstance(obj, Path3DCollection): + msg = f"Expected Path3DCollection, got {type(obj)}." + raise TypeError(msg) + offsets = obj._offsets3d # noqa: SLF001 + xdata, ydata, zdata = (np.ma.getdata(offset) for offset in offsets) + return np.column_stack([xdata, ydata, zdata]) + + +def get_segments3d(obj: LineCollection) -> list[np.ndarray]: + if not isinstance(obj, Line3DCollection): + msg = f"Expected Line3DCollection, got {type(obj)}." + raise TypeError(msg) + segments = obj._segments3d # noqa: SLF001 + return [np.asarray(segment, dtype=float) for segment in segments] + + +def get_poly3d_segments(obj: Collection) -> list[np.ndarray]: + if not isinstance(obj, Poly3DCollection): + msg = f"Expected Poly3DCollection, got {type(obj)}." + raise TypeError(msg) + + # Matplotlib <= 3.10 stores Poly3DCollection vertices in _vec/_segslices. + if hasattr(obj, "_vec") and hasattr(obj, "_segslices"): + vec = obj._vec # noqa: SLF001 + segslices = obj._segslices # noqa: SLF001 + return [np.asarray(vec[:3, segslice].T, dtype=float) for segslice in segslices] + + # Matplotlib 3.11.0rc1 switched to padded _faces plus _invalid_vertices. + faces = getattr(obj, "_faces", None) + if faces is None: + msg = "Poly3DCollection has neither _vec/_segslices nor _faces." + raise AttributeError(msg) + + faces = np.asarray(faces, dtype=float) + if faces.ndim != 3 or faces.shape[-1] != 3: # noqa: PLR2004 + msg = f"Expected Poly3DCollection._faces with shape (n, m, 3), got {faces.shape}." + raise ValueError(msg) + + invalid_vertices = np.asarray(getattr(obj, "_invalid_vertices", False), dtype=bool) + if invalid_vertices.ndim == 0: + if bool(invalid_vertices): + return [] + return [np.asarray(face, dtype=float) for face in faces] + + return [ + np.asarray(face[~invalid_mask], dtype=float) + for face, invalid_mask in zip(faces, invalid_vertices, strict=False) + ] + + +def get_poly3d_facecolors(obj: Collection) -> np.ndarray: + facecolors = obj._facecolor3d # type: ignore[attr-defined] # noqa: SLF001 + return np.asarray(facecolors) + + +def get_poly3d_edgecolors(obj: Collection) -> np.ndarray: + edgecolors = obj._edgecolor3d # type: ignore[attr-defined] # noqa: SLF001 + return np.asarray(edgecolors) + + +def get_contour3d_verts_and_codes(obj: Collection) -> list[tuple[np.ndarray, np.ndarray | None]]: + verts_codes = obj._3dverts_codes # type: ignore[attr-defined] # noqa: SLF001 + return [ + ( + np.asarray(vertices, dtype=float), + None if codes is None else np.asarray(codes), + ) + for vertices, codes in verts_codes + ] + + +def draw_line3d(data: TikzData, obj: Line2D) -> list[str]: + """Return PGFPlots code for a 3D line.""" + coordinates = get_line3d_data(obj) + segments = _line_segments_for_export(data, coordinates) + if not segments: + return [] + + addplot_options = _line2d._get_line2d_options(data, obj) # noqa: SLF001 + legend_text = get_legend_text(obj) + if legend_text is None and obj.axes is not None and has_legend(obj.axes): + addplot_options.append("forget plot") + + content = [] + for segment in segments: + content.append("\\addplot3 ") + if addplot_options: + content.append("[{}]\n".format(", ".join(addplot_options))) + content.extend(table(data, segment)) + + if legend_text is not None: + content.append(f"\\addlegendentry{{{legend_text}}}\n") + + return content + + +def draw_quiver3d(data: TikzData, obj: Line3DCollection) -> list[str]: + """Return PGFPlots code for a 3D quiver plot.""" + if data.clip_3d == "clip": + return draw_line3dcollection(data, obj) + + style_arrays = _poly3d_style_arrays(obj) + segments = get_segments3d(obj) + quiver_data = _quiver3d_data(data, obj, segments, style_arrays) + if quiver_data is not None: + return addplot_table( + data, + quiver_data.coordinates, + command="addplot3", + options=quiver_data.options, + table_options=["x=x", "y=y", "z=z"], + column_names=["x", "y", "z", "u", "v", "w"], + ) + return [] + + +def draw_line3dcollection(data: TikzData, obj: Line3DCollection) -> list[str]: + """Return PGFPlots code for 3D line collections such as wireframes and quivers.""" + style_arrays = _poly3d_style_arrays(obj) + segments = get_segments3d(obj) + + content = [] + for i, segment in enumerate(segments): + color = _cycle_array(style_arrays.edgecolors, i) + style = _cycle_array(style_arrays.linestyles, i) + if isinstance(style, tuple): + style = (float(style[0]), style[1]) + linewidth = _cycle_array(style_arrays.linewidths, i) + width = None if linewidth is None else float(linewidth) + + options = mypath.get_draw_options( + data, mypath.LineData(obj=obj, ec=color, ls=style, lw=width) + ) + for export_segment in _line_segments_for_export(data, segment): + content.extend( + addplot_table( + data, + export_segment, + command="addplot3", + options=options, + externalize_min_rows=3, + ) + ) + + return content + + +def _line_segments_for_export(data: TikzData, points: np.ndarray) -> list[np.ndarray]: + if len(points) == 0: + return [] + clip_box = _clip_box(data) + if clip_box is None: + return [points] + return clip_line_to_box(points, clip_box, data.clip_3d) + + +def _clip_box(data: TikzData) -> ClipBox3D | None: + if data.clip_3d == "none" or data.current_mpl_axes is None: + return None + return clip_box_from_axes(data.current_mpl_axes) + + +def _quiver3d_data( + data: TikzData, + obj: LineCollection, + segments: list[np.ndarray], + style_arrays: Poly3DStyleArrays, +) -> Quiver3DData | None: + quiver_coordinates = _quiver3d_coordinates(segments) + if quiver_coordinates is None: + return None + quiver_coordinates = _clip_quiver_coordinates(data, quiver_coordinates) + if len(quiver_coordinates) == 0: + return None + + style = _cycle_array(style_arrays.linestyles, 0) + if isinstance(style, tuple): + style = (float(style[0]), style[1]) + linewidth = _cycle_array(style_arrays.linewidths, 0) + width = None if linewidth is None else float(linewidth) + options = mypath.get_draw_options( + data, + mypath.LineData(obj=obj, ec=_cycle_array(style_arrays.edgecolors, 0), ls=style, lw=width), + ) + options.extend( + [ + "-latex", + "mark=none", + r"quiver={u=\thisrow{u}, v=\thisrow{v}, w=\thisrow{w}}", + ] + ) + return Quiver3DData(quiver_coordinates, options) + + +def _quiver3d_coordinates(segments: list[np.ndarray]) -> np.ndarray | None: + if not segments or len(segments) % 3 != 0: + return None + + arrow_count = len(segments) // 3 + shafts = segments[:arrow_count] + first_heads = segments[arrow_count : 2 * arrow_count] + second_heads = segments[2 * arrow_count :] + rows = [] + for shaft, first_head, second_head in zip(shafts, first_heads, second_heads, strict=True): + if shaft.shape != (2, 3) or first_head.shape != (2, 3) or second_head.shape != (2, 3): + return None + tip = shaft[0] + tail = shaft[1] + if not (np.allclose(first_head[0], tip) and np.allclose(second_head[0], tip)): + return None + vector = tip - tail + if np.allclose(vector, 0.0): + return None + rows.append([*tail, *vector]) + + return np.asarray(rows, dtype=float) + + +def _quiver3d_has_uniform_style(style_arrays: Poly3DStyleArrays) -> bool: + return ( + _array_len(style_arrays.edgecolors) <= 1 + and _array_len(style_arrays.linewidths) <= 1 + and len(style_arrays.linestyles) <= 1 + ) + + +def _array_len(values: Sequence[object] | np.ndarray | None) -> int: + return 0 if values is None else len(values) + + +def _clip_quiver_coordinates(data: TikzData, coordinates: np.ndarray) -> np.ndarray: + clip_box = _clip_box(data) + if clip_box is None: + return coordinates + tails = coordinates[:, :3] + tips = coordinates[:, :3] + coordinates[:, 3:6] + mask = points_inside(tails, clip_box) & points_inside(tips, clip_box) + return coordinates[mask] + + +def draw_path3dcollection(data: TikzData, obj: PathCollection) -> list[str]: + """Return PGFPlots code for 3D scatter/path collections.""" + offsets = get_offsets3d(obj) + mask = _scatter_mask_for_export(data, offsets) + if mask is None: + return _draw_path3dcollection(data, obj, offsets) + if not np.any(mask): + return [] + return _draw_clipped_path3dcollection(data, obj, offsets, mask) + + +def _draw_path3dcollection(data: TikzData, obj: PathCollection, offsets: np.ndarray) -> list[str]: + pcd = mypath.make_pathcollection_data( + data, + obj, + mypath.PathCollectionCoordinates( + offsets=offsets, + labels=["x", "y", "z"], + table_options=["x=x", "y=y", "z=z"], + is_contour=False, + command="\\addplot3", + ), + ) + return mypath.draw_pathcollection_data(data, pcd) + + +def _scatter_mask_for_export(data: TikzData, offsets: np.ndarray) -> np.ndarray | None: + clip_box = _clip_box(data) + if clip_box is None: + return None + return points_inside(offsets, clip_box) + + +def _draw_clipped_path3dcollection( + data: TikzData, obj: PathCollection, offsets: np.ndarray, mask: np.ndarray +) -> list[str]: + obj3d = cast("Path3DCollection", obj) + old_offsets = obj3d._offsets3d # noqa: SLF001 + old_array = obj.get_array() + old_sizes = obj.get_sizes() + old_edgecolors = obj.get_edgecolors() # type: ignore[attr-defined] + old_facecolors = obj.get_facecolors() # type: ignore[attr-defined] + try: + obj3d._offsets3d = tuple(offsets[mask].T) # noqa: SLF001 + if old_array is not None and len(old_array) == len(mask): + obj.set_array(np.asarray(old_array)[mask]) + if len(old_sizes) == len(mask): + obj.set_sizes(old_sizes[mask]) + if len(old_edgecolors) == len(mask): + obj.set_edgecolors(old_edgecolors[mask]) # type: ignore[attr-defined] + if len(old_facecolors) == len(mask): + obj.set_facecolors(old_facecolors[mask]) # type: ignore[attr-defined] + return _draw_path3dcollection(data, obj, offsets[mask]) + finally: + obj3d._offsets3d = old_offsets # noqa: SLF001 + obj.set_array(old_array) + obj.set_sizes(old_sizes) + obj.set_edgecolors(old_edgecolors) # type: ignore[attr-defined] + obj.set_facecolors(old_facecolors) # type: ignore[attr-defined] + + +def draw_poly3dcollection(data: TikzData, obj: Collection) -> list[str]: + """Returns PGFPlots patch-plot code for 3D polygon collections.""" + indexed_segments = _poly_segments_for_export(data, get_poly3d_segments(obj)) + if not indexed_segments: + return [] + + data.pgfplots_libs.add("patchplots") + array = obj.get_array() + if array is not None: + color_data = np.ma.getdata(array) + if len(color_data) > max(index for index, _ in indexed_segments): + segment_colors = [ + (vertices, float(color_data[index])) for index, vertices in indexed_segments + ] + return _draw_poly3dcollection_colormapped(data, obj, segment_colors) + + return _draw_poly3dcollection_explicit_colors(data, obj, indexed_segments) + + +def _poly_segments_for_export( + data: TikzData, segments: list[np.ndarray] +) -> list[tuple[int, np.ndarray]]: + clip_box = _clip_box(data) + if clip_box is None: + return [ + (index, vertices) + for index, vertices in enumerate(segments) + if len(vertices) >= MIN_POLYGON_VERTICES + ] + + indexed_segments: list[tuple[int, np.ndarray]] = [] + for index, vertices in enumerate(segments): + if len(vertices) < MIN_POLYGON_VERTICES: + continue + if data.clip_3d == "clip" and np.all(points_inside(vertices, clip_box)): + indexed_segments.append((index, vertices)) + continue + + clipped = clip_polygon_to_box(vertices, clip_box, data.clip_3d) + if len(clipped) >= MIN_POLYGON_VERTICES: + indexed_segments.append((index, clipped)) + return indexed_segments + + +def draw_contour3d(data: TikzData, obj: Collection) -> list[str]: + """Returns PGFPlots code for 3D contour collections.""" + facecolors = np.asarray(obj.get_facecolor()) + if len(facecolors): + return _draw_contour3d_filled(data, obj, facecolors) + return _draw_contour3d_lines(data, obj) + + +def _draw_contour3d_lines(data: TikzData, obj: Collection) -> list[str]: + content: list[str] = [] + style_arrays = _poly3d_style_arrays(obj) + for i, (vertices, codes) in enumerate(get_contour3d_verts_and_codes(obj)): + options = _poly3d_style_options( + data, obj, _poly3d_style_at(style_arrays, i, include_facecolor=False) + ) + for segment in _split_contour3d_vertices_for_export(data, vertices, codes): + content.extend(addplot_table(data, segment, command="addplot3", options=options)) + return content + + +def _draw_contour3d_filled(data: TikzData, obj: Collection, facecolors: np.ndarray) -> list[str]: + data.pgfplots_libs.add("patchplots") + content: list[str] = [] + style_arrays = _poly3d_style_arrays(obj, facecolors=facecolors) + for i, (vertices, codes) in enumerate(get_contour3d_verts_and_codes(obj)): + segments = _contourf_segments_for_export(data, vertices, codes) + if not segments: + continue + style = _poly3d_style_at(style_arrays, i) + for vertex_count, grouped_segments in _group_segments_only_by_vertex_count(segments): + options = _poly3d_base_options(data, vertex_count) + options.extend(_poly3d_style_options(data, obj, style)) + options.append(_patch_table(data, grouped_segments)) + content.extend(_poly3d_addplot(data, grouped_segments, options)) + return content + + +def _split_contour3d_vertices_for_export( + data: TikzData, vertices: np.ndarray, codes: np.ndarray | None +) -> list[np.ndarray]: + segments = _split_contour3d_vertices(vertices, codes) + clip_box = _clip_box(data) + if clip_box is None: + return [segment for segment in segments if len(segment) >= 2] # noqa: PLR2004 + + export_segments = [] + for segment in segments: + export_segments.extend(clip_line_to_box(segment, clip_box, data.clip_3d)) + return export_segments + + +def _contourf_segments_for_export( + data: TikzData, vertices: np.ndarray, codes: np.ndarray | None +) -> list[np.ndarray]: + segments = _split_contour3d_vertices(vertices, codes) + clip_box = _clip_box(data) + if clip_box is None: + return [segment for segment in segments if len(segment) >= MIN_POLYGON_VERTICES] + + export_segments = [] + for segment in segments: + clipped = clip_polygon_to_box(segment, clip_box, data.clip_3d) + if len(clipped) >= MIN_POLYGON_VERTICES: + export_segments.append(clipped) + return export_segments + + +def _split_contour3d_vertices(vertices: np.ndarray, codes: np.ndarray | None) -> list[np.ndarray]: + if len(vertices) == 0: + return [] + if codes is None: + return [vertices] + + segments: list[np.ndarray] = [] + current_segment: list[np.ndarray] = [] + for vertex, code in zip(vertices, codes, strict=False): + if code == Path.MOVETO: + if current_segment: + segments.append(np.asarray(current_segment, dtype=float)) + current_segment = [vertex] + elif code == Path.CLOSEPOLY: + if current_segment: + segments.append(np.asarray(current_segment, dtype=float)) + current_segment = [] + else: + current_segment.append(vertex) + if current_segment: + segments.append(np.asarray(current_segment, dtype=float)) + return segments + + +def _draw_poly3dcollection_colormapped( + data: TikzData, obj: Collection, segment_colors: list[tuple[np.ndarray, float]] +) -> list[str]: + content: list[str] = [] + cmap = obj.get_cmap() + if cmap is not None: + mycolormap, is_custom_cmap = _mpl_cmap2pgf_cmap(cmap, data) + colormap_option = "colormap" + ("=" if is_custom_cmap else "/") + mycolormap + data.current_axis_options.add(colormap_option) + + # Triangulate if shading is enabled and any polygon has more than 4 vertices, since PGFPlots + # only supports shading for triangles and bilinear shading for quads. + if data.shader != "none" and any(len(vertices) > 4 for vertices, _ in segment_colors): # noqa: PLR2004 + segment_colors = [ + (segment, color_value) + for vertices, color_value in segment_colors + for segment in _triangulate_polygon(vertices) + ] + + for vertex_count, group in _group_segments_by_vertex_count(segment_colors): + grouped_segments = [segment for segment, _ in group] + grouped_color_data = [color_value for _, color_value in group] + options = _poly3d_base_options(data, vertex_count, use_shader=data.shader != "none") + options.extend(_poly3d_collection_options(data, obj)) + + if data.shader != "none": + options.append(r"point meta=\thisrow{meta}") + options.append(_patch_table(data, grouped_segments)) + content.extend( + _poly3d_addplot( + data, + grouped_segments, + options, + point_meta_segments=_poly3d_z_point_meta(grouped_segments), + ) + ) + else: + options.append(_patch_table(data, grouped_segments, grouped_color_data)) + content.extend(_poly3d_addplot(data, grouped_segments, options)) + + return content + + +def _triangulate_polygon(vertices: np.ndarray) -> list[np.ndarray]: + if len(vertices) < MIN_POLYGON_VERTICES: + return [] + if len(vertices) == MIN_POLYGON_VERTICES: + return [vertices] + return [ + np.asarray([vertices[0], vertices[i], vertices[i + 1]], dtype=float) + for i in range(1, len(vertices) - 1) + ] + + +def _draw_poly3dcollection_explicit_colors( + data: TikzData, obj: Collection, segments: list[tuple[int, np.ndarray]] +) -> list[str]: + facecolors = get_poly3d_facecolors(obj) + edgecolors = get_poly3d_edgecolors(obj) + style_arrays = _poly3d_style_arrays(obj, facecolors=facecolors, edgecolors=edgecolors) + + group_key: tuple[int, HashableColor | None, float | None, str, float | None] + grouped: dict[ + tuple[int, HashableColor | None, float | None, str, float | None], Poly3DColorGroup + ] = {} + for index, vertices in segments: + style = _poly3d_style_at(style_arrays, index) + fc = style.facecolor + ec = style.edgecolor + lw = style.linewidth + ls = style.linestyle + face_alpha = _color_alpha(fc) + group_key = ( + len(vertices), + _hashable_color(ec), + float(lw) if lw is not None else None, + repr(ls), + face_alpha, + ) + if group_key not in grouped: + grouped[group_key] = Poly3DColorGroup( + segments=[], + colors=[], + style_options=_poly3d_edge_options(data, obj, ec, lw, ls) + + _poly3d_fill_opacity_options(data, face_alpha), + vertex_count=len(vertices), + ) + grouped[group_key].segments.append(vertices) + grouped[group_key].colors.append(_rgb_color(fc)) + + content: list[str] = [] + for group in grouped.values(): + options = _poly3d_base_options(data, group.vertex_count) + options.extend(group.style_options) + color_indices = _poly3d_explicit_color_indices(group.colors) + if not color_indices.unique_colors: + options.append(_patch_table(data, group.segments)) + elif len(color_indices.unique_colors) > 1: + options.extend(_poly3d_colormap_options(data, color_indices.unique_colors)) + options.append(_patch_table(data, group.segments, color_indices.indices)) + else: + color = color_indices.unique_colors[0] + options.extend( + _poly3d_style_options( + data, + obj, + Poly3DStyle(color, None, None, None), + ) + ) + options.append(_patch_table(data, group.segments)) + content.extend(_poly3d_addplot(data, group.segments, options)) + return content + + +@dataclass +class Poly3DColorIndices: + unique_colors: list[np.ndarray] + indices: list[float] + + +def _poly3d_base_options( + data: TikzData, vertex_count: int, *, use_shader: bool = False +) -> list[str]: + options = [ + "patch", + *_poly3d_patch_type_options(use_shader, vertex_count), + "table/row sep=\\\\", + "z buffer=sort", + ] + if use_shader: + options.append(_shader_option(data.shader)) + return options + + +def _poly3d_patch_type_options(use_shader: bool, vertex_count: int) -> list[str]: # noqa: FBT001 + if use_shader and vertex_count == 3: # noqa: PLR2004 + return ["patch type=triangle"] + if use_shader and vertex_count == 4: # noqa: PLR2004 + return ["patch type=bilinear"] + return ["patch type=polygon", f"vertex count={vertex_count}"] + + +def _shader_option(shader: str) -> str: + shader = shader.removeprefix(",").strip() + return shader if shader.startswith("shader=") else f"shader={shader}" + + +def _poly3d_collection_options(data: TikzData, obj: Collection) -> list[str]: + facecolors = get_poly3d_facecolors(obj) + edgecolors = get_poly3d_edgecolors(obj) + style = _poly3d_style_at( + _poly3d_style_arrays(obj, facecolors=facecolors, edgecolors=edgecolors), 0 + ) + face_alpha = _color_alpha(style.facecolor) + return _poly3d_edge_options( + data, + obj, + style.edgecolor, + style.linewidth, + style.linestyle, + ) + _poly3d_fill_opacity_options(data, face_alpha) + + +def _poly3d_edge_options( + data: TikzData, + obj: Collection, + edgecolor: np.ndarray | None, + linewidth: float | None, + linestyle: LineStyle, +) -> list[str]: + return _poly3d_style_options( + data, + obj, + Poly3DStyle( + facecolor=None, + edgecolor=edgecolor, + linewidth=linewidth, + linestyle=linestyle, + ), + ) + + +def _poly3d_style_arrays( + obj: Collection, + *, + facecolors: np.ndarray | None = None, + edgecolors: np.ndarray | None = None, +) -> Poly3DStyleArrays: + return Poly3DStyleArrays( + facecolors=facecolors, + edgecolors=np.asarray(obj.get_edgecolor()) if edgecolors is None else edgecolors, + linewidths=np.atleast_1d(obj.get_linewidth()), + linestyles=cast("Sequence[LineStyle]", obj.get_linestyle()), + ) + + +def _poly3d_style_at( + style_arrays: Poly3DStyleArrays, + index: int, + *, + include_facecolor: bool = True, +) -> Poly3DStyle: + return Poly3DStyle( + facecolor=_cycle_array(style_arrays.facecolors, index) if include_facecolor else None, + edgecolor=_cycle_array(style_arrays.edgecolors, index), + linewidth=_cycle_array(style_arrays.linewidths, index), + linestyle=_cycle_array(style_arrays.linestyles, index), + ) + + +def _poly3d_style_options( + data: TikzData, + obj: Collection, + style: Poly3DStyle, +) -> list[str]: + line_data = mypath.LineData( + obj=obj, + ec=style.edgecolor, + fc=style.facecolor, + ls=style.linestyle if isinstance(style.linestyle, (str, tuple)) else None, + lw=style.linewidth, + ) + draw_options = mypath.get_draw_options(data, line_data) + if ( + (style.edgecolor is None or style.linewidth == 0) + and "draw=none" not in draw_options + and not any(option.startswith("draw=") for option in draw_options) + ): + draw_options.append("draw=none") + if ( + style.facecolor is not None + and len(style.facecolor) == RGBA_LENGTH + and style.facecolor[3] == 0 + ): + draw_options.append("fill opacity=0") + return draw_options + + +def _poly3d_explicit_color_indices(colors: list[np.ndarray | None]) -> Poly3DColorIndices: + non_null_colors = [color for color in colors if color is not None] + if len(non_null_colors) != len(colors): + return Poly3DColorIndices([], []) + + unique_colors: list[np.ndarray] = [] + indices: list[float] = [] + for color in non_null_colors: + color_index = _matching_color_index(unique_colors, color) + if color_index is None: + color_index = len(unique_colors) + unique_colors.append(color) + indices.append(float(color_index)) + return Poly3DColorIndices(unique_colors, indices) + + +def _matching_color_index(colors: list[np.ndarray], color: np.ndarray) -> int | None: + for i, unique_color in enumerate(colors): + if np.allclose(color, unique_color): + return i + return None + + +def _poly3d_colormap_options(data: TikzData, colors: list[np.ndarray]) -> list[str]: + name = f"matplot2tikzpoly{data.custom_colormap_id}" + data.custom_colormap_id += 1 + + color_changes: list[str] = [] + for i, color in enumerate(colors): + red, green, blue = color[:3] + ff = data.float_format + color_changes.append(f"rgb({i}pt)=({red:{ff}},{green:{ff}},{blue:{ff}})") + + return [ + "colormap={" + name + "}{[1pt]\n " + ";\n ".join(color_changes) + "\n}", + "point meta min=0", + f"point meta max={len(colors) - 1}", + ] + + +def _poly3d_z_point_meta(segments: list[np.ndarray]) -> list[np.ndarray]: + return [np.asarray(segment[:, 2], dtype=float) for segment in segments] + + +def _poly3d_addplot( + data: TikzData, + segments: list[np.ndarray], + options: list[str], + *, + point_meta_segments: list[np.ndarray] | None = None, +) -> list[str]: + column_names = "x y z meta" if point_meta_segments is not None else "x y z" + content = [ + "\\addplot3 [\n", + ",\n".join(options), + "\n]\n", + "table [row sep=\\\\] {%\n", + column_names + "\\\\\n", + ] + + ff = data.float_format + if point_meta_segments is None: + for segment in segments: + for x, y, z in segment: + content.append(f"{x:{ff}} {y:{ff}} {z:{ff}}\\\\\n") + else: + for segment, point_meta in zip(segments, point_meta_segments, strict=True): + for (x, y, z), meta in zip(segment, point_meta, strict=True): + content.append(f"{x:{ff}} {y:{ff}} {z:{ff}} {meta:{ff}}\\\\\n") + content.append("};\n") + return content + + +def _patch_table( + data: TikzData, segments: list[np.ndarray], color_data: list[float] | None = None +) -> str: + rows: list[str] = [] + first_index = 0 + for i, segment in enumerate(segments): + indices = [str(index) for index in range(first_index, first_index + len(segment))] + first_index += len(segment) + if color_data is not None: + indices.append(_format_value(color_data[i], data.float_format)) + rows.append(" ".join(indices) + r"\\") + + key = "patch table with point meta" if color_data is not None else "patch table" + return key + "={%\n" + "\n".join(rows) + "\n}" + + +def _group_segments_by_vertex_count( + segment_colors: list[tuple[np.ndarray, float]], +) -> Iterable[tuple[int, list[tuple[np.ndarray, float]]]]: + grouped: dict[int, list[tuple[np.ndarray, float]]] = {} + for segment, color_value in segment_colors: + grouped.setdefault(len(segment), []).append((segment, float(color_value))) + return grouped.items() + + +def _group_segments_only_by_vertex_count( + segments: list[np.ndarray], +) -> Iterable[tuple[int, list[np.ndarray]]]: + grouped: dict[int, list[np.ndarray]] = {} + for segment in segments: + grouped.setdefault(len(segment), []).append(segment) + return grouped.items() + + +def _cycle_array(values: Sequence[T] | np.ndarray | None, index: int) -> T | None: + if values is None: + return None + length = len(values) + if length == 0: + return None + return values[index % length] + + +def _hashable_color(color: np.ndarray | None) -> HashableColor | None: + if color is None: + return None + return tuple(float(value) for value in np.asarray(color).reshape(-1)) + + +def _rgb_color(color: np.ndarray | None) -> np.ndarray | None: + if color is None: + return None + color_array = np.asarray(color, dtype=float).reshape(-1) + if len(color_array) < 3: # noqa: PLR2004 + return None + return color_array[:3] + + +def _color_alpha(color: np.ndarray | None) -> float | None: + if color is None: + return None + color_array = np.asarray(color, dtype=float).reshape(-1) + if len(color_array) < RGBA_LENGTH: + return None + return float(color_array[3]) + + +def _poly3d_fill_opacity_options(data: TikzData, alpha: float | None) -> list[str]: + if alpha is None or alpha == 1.0: + return [] + return [f"fill opacity={alpha:{data.float_format}}"] + + +def addplot_table( # noqa: PLR0913 + data: TikzData, + coordinates: np.ndarray, + *, + command: str = "addplot", + options: Sequence[str] | None = None, + table_options: Sequence[str] | None = None, + column_names: Sequence[str] | None = None, + externalize_min_rows: int = 3, +) -> list[str]: + """Return a PGFPlots addplot command with inline or external table data.""" + coordinates = _as_2d_array(coordinates) + if len(coordinates) == 0: + return [] + + content = [f"\\{command}"] + if options: + content.append(" [" + ", ".join(options) + "]") + content.append("\n") + content.extend( + table( + data, + coordinates, + table_options=table_options, + column_names=column_names, + externalize_min_rows=externalize_min_rows, + ) + ) + return content + + +def table( + data: TikzData, + coordinates: np.ndarray, + *, + table_options: Sequence[str] | None = None, + column_names: Sequence[str] | None = None, + externalize_min_rows: int = 3, +) -> list[str]: + """Return PGFPlots table code for numeric coordinate data.""" + coordinates = _as_2d_array(coordinates) + if not np.all(np.isfinite(coordinates)) and "unbounded coords=jump" not in ( + data.current_axis_options + ): + data.current_axis_options.add("unbounded coords=jump") + + opts = list(table_options or []) + if data.table_row_sep != "\n": + opts.append("row sep=" + data.table_row_sep.strip()) + + plot_table = table_rows(data, coordinates, column_names=column_names) + content = [] + if data.externalize_tables and len(coordinates) >= externalize_min_rows: + filepath, rel_filepath = _files.new_filepath(data, "table", ".dat") + with filepath.open("w") as f: + f.write("".join(plot_table)) + + if data.externals_search_path is not None: + opts.append(f"search path={{{data.externals_search_path}}}") + + opts_str = ("[" + ",".join(opts) + "] ") if opts else "" + content.append(f"table {opts_str}{{{rel_filepath.as_posix()}}};\n") + return content + + if opts: + content.append("table [" + ",".join(opts) + "] {%\n") + else: + content.append("table {%\n") + content.extend(plot_table) + content.append("};\n") + return content + + +def table_rows( + data: TikzData, + coordinates: np.ndarray, + *, + column_names: Sequence[str] | None = None, +) -> list[str]: + coordinates = _as_2d_array(coordinates) + ff = data.float_format + table_row_sep = data.table_row_sep + rows = [] + if column_names: + rows.append(" ".join(column_names) + table_row_sep) + rows.extend( + " ".join(_format_value(value, ff) for value in row) + table_row_sep for row in coordinates + ) + return rows + + +def _as_2d_array(coordinates: np.ndarray) -> np.ndarray: + coordinates = np.asarray(coordinates) + if coordinates.ndim == 1: + return coordinates.reshape((-1, 1)) + if coordinates.ndim != 2: # noqa: PLR2004 + msg = f"Expected 2D coordinate array, got shape {coordinates.shape}." + raise ValueError(msg) + return coordinates + + +def _format_value(value: str | float | np.number, float_format: str) -> str: + if isinstance(value, str): + return value + if isinstance(value, Iterable): + msg = f"Unexpected nested table value {value!r}." + raise TypeError(msg) + numeric_value = value + if not isinstance(numeric_value, (int, float, np.number)): + msg = f"Unexpected table value {value!r}." + raise TypeError(msg) + return f"{numeric_value:{float_format}}" diff --git a/src/matplot2tikz/_path.py b/src/matplot2tikz/_path.py index e1da1cd0..80a9a185 100644 --- a/src/matplot2tikz/_path.py +++ b/src/matplot2tikz/_path.py @@ -27,10 +27,10 @@ @dataclass class LineData: obj: Collection | Patch - ec: str | tuple | None = None # edgecolor + ec: str | tuple | np.ndarray | None = None # edgecolor ec_name: str | None = None ec_rgba: np.ndarray | None = None - fc: str | tuple | None = None # facecolor + fc: str | tuple | np.ndarray | None = None # facecolor fc_name: str | None = None fc_rgba: np.ndarray | None = None ls: str | tuple[float, Sequence[float] | None] | None = None # linestyle @@ -42,16 +42,26 @@ class LineData: class PathCollectionData: obj: PathCollection dd_strings: np.ndarray - draw_options: list - labels: list - table_options: list + draw_options: list[str] + labels: list[str] + table_options: list[str] is_contour: bool + command: str = "\\addplot" marker: str | None = None is_filled: bool = False add_individual_color_code: bool | None = False legend_text: str | None = None +@dataclass +class PathCollectionCoordinates: + offsets: np.ndarray + labels: list[str] + table_options: list[str] + is_contour: bool + command: str + + def draw_path( data: TikzData, path: Path, @@ -162,31 +172,32 @@ def _check_x_is_date(data: TikzData) -> bool: def draw_pathcollection(data: TikzData, obj: PathCollection) -> list[str]: """Returns PGFPlots code for a number of patch objects.""" - content = [] - # gather data - dd = obj.get_offsets() - if not isinstance(dd, Iterable): - # No idea what to draw. - return [] - - path_collection_data = PathCollectionData( - obj=obj, - dd_strings=np.array( - [ - [f"{val:{data.float_format}}" for val in row] # type: ignore[str-bytes-safe] - for row in dd - if isinstance(row, Iterable) - ] + offsets = np.asarray(obj.get_offsets()) + path_collection_data = make_pathcollection_data( + data, + obj, + PathCollectionCoordinates( + offsets=offsets, + labels=["x", "y"], + table_options=[], + is_contour=len(offsets) == 1, + command="\\addplot", ), - draw_options=["only marks"], - labels=["x", "y"], - table_options=[], - is_contour=isinstance(dd, Sized) and len(dd) == 1, ) + return draw_pathcollection_data(data, path_collection_data) + + +def draw_pathcollection_data(data: TikzData, path_collection_data: PathCollectionData) -> list[str]: + content = [] + obj = path_collection_data.obj line_data = LineData(obj=obj) if obj.get_array() is not None: _draw_pathcollection_scatter_colormap(data, path_collection_data) + _draw_pathcollection_get_edgecolors(data, path_collection_data, line_data) + _draw_pathcollection_get_marker(path_collection_data) + _draw_pathcollection_get_linewidth(path_collection_data, line_data) + path_collection_data.is_filled = True else: # gather the draw options _draw_pathcollection_get_edgecolors(data, path_collection_data, line_data) @@ -202,7 +213,7 @@ def draw_pathcollection(data: TikzData, obj: PathCollection) -> list[str]: for path in obj.get_paths(): _draw_pathcollection_draw_contour(path, data, path_collection_data) - _draw_pathcollection_scatter_sizes(path_collection_data) + _draw_pathcollection_scatter_sizes(data, path_collection_data) # remove duplicates draw_options = sorted(set(path_collection_data.draw_options)) @@ -211,7 +222,7 @@ def draw_pathcollection(data: TikzData, obj: PathCollection) -> list[str]: len_row = sum(len(item) for item in draw_options) j0, j1, j2 = ("", ", ", "") if len_row < max_row_length else ("\n ", ",\n ", "\n") do = f" [{j0}{{}}{j2}]".format(j1.join(draw_options)) if draw_options else "" - content.append(f"\\addplot{do}\n") + content.append(f"{path_collection_data.command}{do}\n") if data.externals_search_path is not None: esp = data.externals_search_path @@ -245,13 +256,46 @@ def draw_pathcollection(data: TikzData, obj: PathCollection) -> list[str]: return content +def make_pathcollection_data( + data: TikzData, + obj: PathCollection, + coordinates: PathCollectionCoordinates, +) -> PathCollectionData: + dd = coordinates.offsets + if not isinstance(dd, Iterable): + msg = f"Expected iterable path collection offsets, got {type(dd)}." + raise TypeError(msg) + + dd_strings = [] + for row in dd: + if not isinstance(row, Iterable): + msg = f"Expected iterable path collection offset row, got {row!r}." + raise TypeError(msg) + dd_strings.append([f"{val:{data.float_format}}" for val in row]) + + return PathCollectionData( + obj=obj, + dd_strings=np.array(dd_strings), + draw_options=["only marks"], + labels=coordinates.labels, + table_options=coordinates.table_options, + is_contour=coordinates.is_contour, + command=coordinates.command, + ) + + def _draw_pathcollection_scatter_colormap(data: TikzData, pcd: PathCollectionData) -> None: obj_array = pcd.obj.get_array() if obj_array is not None: - pcd.dd_strings = np.column_stack([pcd.dd_strings, obj_array]) + colordata = [f"{value:{data.float_format}}" for value in np.ma.getdata(obj_array)] + pcd.dd_strings = np.column_stack([pcd.dd_strings, colordata]) pcd.labels.append("colordata") pcd.draw_options.append("scatter src=explicit") - pcd.table_options.extend(["x=x", "y=y", "meta=colordata"]) + coordinate_options = [f"{label}={label}" for label in pcd.labels if label in {"x", "y", "z"}] + for option in coordinate_options: + if option not in pcd.table_options: + pcd.table_options.append(option) + pcd.table_options.append("meta=colordata") if pcd.obj.get_cmap(): mycolormap, is_custom_cmap = _mpl_cmap2pgf_cmap(pcd.obj.get_cmap(), data) pcd.draw_options.append("scatter") @@ -268,7 +312,7 @@ def _draw_pathcollection_get_edgecolors( else: if len(edgecolors) == 1: line_data.ec = edgecolors[0] - elif len(edgecolors) > 1: + elif len(edgecolors) == len(pcd.dd_strings): pcd.labels.append("draw") ec_strings = [ @@ -290,7 +334,7 @@ def _draw_pathcollection_get_facecolors( if len(facecolors) == 1: line_data.fc = facecolors[0] pcd.is_filled = True - elif len(facecolors) > 1: + elif len(facecolors) == len(pcd.dd_strings): pcd.labels.append("fill") fc_strings = [ ",".join(f"{item:{data.float_format}}" for item in row) @@ -301,6 +345,12 @@ def _draw_pathcollection_get_facecolors( pcd.is_filled = True +def _draw_pathcollection_get_linewidth(pcd: PathCollectionData, line_data: LineData) -> None: + linewidths = np.atleast_1d(pcd.obj.get_linewidth()) + if len(linewidths) == 1: + line_data.lw = float(linewidths[0]) + + def _draw_pathcollection_add_individual_color(pcd: PathCollectionData) -> None: if pcd.add_individual_color_code: pcd.draw_options.extend( @@ -387,11 +437,11 @@ def _draw_pathcollection_draw_contour(path: Path, data: TikzData, pcd: PathColle pcd.dd_strings = np.array(dd_strings[1:], dtype=object) -def _draw_pathcollection_scatter_sizes(pcd: PathCollectionData) -> None: +def _draw_pathcollection_scatter_sizes(data: TikzData, pcd: PathCollectionData) -> None: if len(pcd.obj.get_sizes()) == len(pcd.dd_strings): # See Pgfplots manual, chapter 4.25. # In Pgfplots, \mark size specifies radii, in matplotlib circle areas. - radii = np.sqrt(pcd.obj.get_sizes() / np.pi) + radii = [f"{radius:{data.float_format}}" for radius in np.sqrt(pcd.obj.get_sizes() / np.pi)] pcd.dd_strings = np.column_stack([pcd.dd_strings, radii]) pcd.labels.append("sizedata") pcd.draw_options.extend( diff --git a/src/matplot2tikz/_save.py b/src/matplot2tikz/_save.py index c3763c3c..aefd988d 100644 --- a/src/matplot2tikz/_save.py +++ b/src/matplot2tikz/_save.py @@ -5,7 +5,7 @@ import tempfile import warnings from pathlib import Path -from typing import TYPE_CHECKING, TypedDict +from typing import TYPE_CHECKING, Literal, TypedDict, cast import matplotlib as mpl import matplotlib.pyplot as plt @@ -20,17 +20,26 @@ from matplotlib.patches import Patch from matplotlib.spines import Spine from matplotlib.text import Text +from mpl_toolkits.mplot3d import Axes3D +from mpl_toolkits.mplot3d.art3d import Line3D, Line3DCollection, Path3DCollection, Poly3DCollection from typing_extensions import NotRequired, Unpack if TYPE_CHECKING: + from collections.abc import Sequence + from matplotlib.artist import Artist -from . import _axes, _legend, _line2d, _patch, _path, _text, _util + from ._clip3d import Clip3DMode + + +from . import _axes, _legend, _line2d, _mplot3d, _patch, _path, _text, _util from . import _image as img from . import _quadmesh as qmsh from .__about__ import __version__ from ._tikzdata import Flavors, TikzData +ShaderMode = Literal["none", "interp"] + # Set logger to be used to print some info LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.INFO) @@ -61,11 +70,19 @@ class TikzArgs(TypedDict): show_info: NotRequired[bool] include_disclaimer: NotRequired[bool] standalone: NotRequired[bool] + clip_3d: NotRequired[Clip3DMode] + shader: NotRequired[ShaderMode] float_format: NotRequired[str] table_row_sep: NotRequired[str] flavor: NotRequired[str] +def _validate_clip_3d(clip_3d: str) -> None: + if clip_3d not in ("none", "hide", "clip"): + msg = 'clip_3d must be one of "none", "hide", or "clip".' + raise ValueError(msg) + + def get_tikz_code( # noqa: PLR0913 figure: str | Figure = "gcf", filepath: str | Path | None = None, @@ -87,6 +104,8 @@ def get_tikz_code( # noqa: PLR0913 show_info: bool = False, # noqa: FBT001, FBT002 include_disclaimer: bool = True, # noqa: FBT001, FBT002 standalone: bool = False, # noqa: FBT001, FBT002 + clip_3d: Clip3DMode = "none", + shader: ShaderMode = "none", float_format: str = ".15g", table_row_sep: str = "\n", flavor: str = "latex", @@ -174,6 +193,16 @@ def get_tikz_code( # noqa: PLR0913 :param standalone: Include wrapper code for a standalone LaTeX file. :type standalone: bool + :param clip_3d: How 3D artists outside the 3D axis limits are handled. + ``"none"`` preserves the current export behavior, + ``"hide"`` removes artists outside the limits, and + ``"clip"`` clips supported lines and polygons to the limits. + :type clip_3d: str + + :param shader: Optional PGFPlots shader value for exported 3D patch plots, + for example ``"interp"`` to emit ``shader=interp``. + :type shader: str + :param float_format: Format for float entities. Default is ```".15g"```. :type float_format: str @@ -202,6 +231,8 @@ def get_tikz_code( # noqa: PLR0913 f"Unsupported TeX flavor {flavor!r}. Please choose from {', '.join(map(repr, Flavors))}" ) raise ValueError(msg) from None + _validate_clip_3d(clip_3d) + data = TikzData(flavor=flavor_object) data.externalize_tables = externalize_tables @@ -212,6 +243,8 @@ def get_tikz_code( # noqa: PLR0913 data.show_info = show_info data.strict = strict data.standalone = standalone + data.clip_3d = clip_3d + data.shader = shader data.axis_width, data.axis_height = axis_width, axis_height if tex_relative_path_to_data is not None: @@ -242,8 +275,11 @@ def get_tikz_code( # noqa: PLR0913 if show_info: _print_pgfplot_libs_message(data) + mpl_figure = _get_figure(figure) + _finalize_figure_for_export(mpl_figure) + # gather the file content - content = _recurse(data, _get_figure(figure)) + content = _recurse(data, mpl_figure) # Check if there is still an open groupplot environment. This occurs if not # all of the group plot slots are used. @@ -262,6 +298,21 @@ def _get_figure(figure: str | Figure) -> Figure: raise ValueError(msg) +def _finalize_figure_for_export(figure: Figure) -> None: + axes3d = [ax for ax in figure.axes if isinstance(ax, Axes3D)] + if not axes3d: + return + + renderer = figure.canvas.get_renderer() # type: ignore[attr-defined] + + for ax in axes3d: + # Axes3D updates aspect-dependent locator state during draw. Do the same + # narrowly, without running collection projection/sorting side effects. + ax._unstale_viewLim() # noqa: SLF001 + locator = ax.get_axes_locator() + ax.apply_aspect(locator(ax, renderer) if locator else None) + + def _set_filepath(data: TikzData, filepath: str | Path | None) -> None: if filepath: filepath = Path(filepath) @@ -294,7 +345,7 @@ def save( f.write(code) -def _generate_code(data: TikzData, content: list) -> str: +def _generate_code(data: TikzData, content: Sequence[str]) -> str: # write disclaimer to the file header code = """""" @@ -323,7 +374,7 @@ def _generate_code(data: TikzData, content: list) -> str: if data.standalone: # When using pdflatex, \\DeclareUnicodeCharacter is necessary. - code = data.flavor.standalone(code) + code = data.flavor.standalone(code, data) return code @@ -332,7 +383,7 @@ def _tex_comment(comment: str) -> str: return "% " + str.replace(comment, "\n", "\n% ") + "\n" -def _get_color_definitions(data: TikzData) -> list: +def _get_color_definitions(data: TikzData) -> list[str]: """Returns the list of custom color definitions for the TikZ file.""" # sort by key sorted_keys = sorted(data.custom_colors.keys(), key=lambda x: x.lower()) @@ -356,14 +407,14 @@ class _ContentManager: def __init__(self) -> None: self._content: dict[float, list[str]] = {} - def extend(self, content: list, zorder: float) -> None: + def extend(self, content: Sequence[str], zorder: float) -> None: """Extends with a list and a z-order.""" if zorder not in self._content: self._content[zorder] = [] self._content[zorder].extend(content) - def flatten(self) -> list: - content_out = [] + def flatten(self) -> list[str]: + content_out: list[str] = [] all_z = sorted(self._content.keys()) for z in all_z: content_out.extend(self._content[z]) @@ -371,16 +422,28 @@ def flatten(self) -> list: def _draw_collection(data: TikzData, child: Collection) -> list[str]: - if isinstance(child, PathCollection): - return _path.draw_pathcollection(data, child) - if isinstance(child, LineCollection): - return _line2d.draw_linecollection(data, child) - if isinstance(child, QuadMesh): - return qmsh.draw_quadmesh(data, child) - return _patch.draw_patchcollection(data, child) + if _mplot3d.is_quiver3d_collection(child): + content = _mplot3d.draw_quiver3d(data, cast("Line3DCollection", child)) + elif _mplot3d.is_contour3d_collection(child): + content = _mplot3d.draw_contour3d(data, child) + elif isinstance(child, Poly3DCollection): + content = _mplot3d.draw_poly3dcollection(data, child) + elif isinstance(child, Path3DCollection): + content = _mplot3d.draw_path3dcollection(data, child) + elif isinstance(child, Line3DCollection): + content = _mplot3d.draw_line3dcollection(data, child) + elif isinstance(child, PathCollection): + content = _path.draw_pathcollection(data, child) + elif isinstance(child, LineCollection): + content = _line2d.draw_linecollection(data, child) + elif isinstance(child, QuadMesh): + content = qmsh.draw_quadmesh(data, child) + else: + content = _patch.draw_patchcollection(data, child) + return content -def _recurse(data: TikzData, obj: Artist) -> list: +def _recurse(data: TikzData, obj: Artist) -> list[str]: """Iterates over all children of the current object and gathers the contents. Content is returned. @@ -418,6 +481,7 @@ def _recurse(data: TikzData, obj: Artist) -> list: else: for child_type, process_func in ( + (Line3D, _mplot3d.draw_line3d), (Line2D, _line2d.draw_line2d), (AxesImage, img.draw_image), (Patch, _patch.draw_patch), @@ -437,6 +501,7 @@ def _recurse(data: TikzData, obj: Artist) -> list: def _process_axes(data: TikzData, obj: Axes, content: _ContentManager) -> None: + data.current_mpl_axes = obj ax = _axes.MyAxes(data, obj) if ax.is_colorbar: @@ -446,8 +511,6 @@ def _process_axes(data: TikzData, obj: Axes, content: _ContentManager) -> None: 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) diff --git a/src/matplot2tikz/_text.py b/src/matplot2tikz/_text.py index 7d46f0ae..a19818c7 100644 --- a/src/matplot2tikz/_text.py +++ b/src/matplot2tikz/_text.py @@ -5,11 +5,19 @@ from typing import TYPE_CHECKING import matplotlib as mpl +import numpy as np from matplotlib.font_manager import font_scalings -from matplotlib.patches import ArrowStyle, BoxStyle, FancyArrowPatch, FancyBboxPatch +from matplotlib.patches import ( + ArrowStyle, + BoxStyle, + FancyArrowPatch, + FancyBboxPatch, +) from matplotlib.text import Annotation, Text +from mpl_toolkits.mplot3d.art3d import Text3D -from . import _color +from . import _color, _mplot3d +from ._clip3d import clip_box_from_axes, points_inside if TYPE_CHECKING: from matplotlib.offsetbox import AnchoredText @@ -30,9 +38,7 @@ def draw_text(data: TikzData, obj: Text) -> list[str]: text = obj.get_text() - if text in ["", data.current_axis_title]: - # Text nodes which are direct children of Axes are typically titles. They are - # already captured by the `title` property of pgfplots axes, so skip them here. + if _is_skipped_text(data, obj, text): return content size = obj.get_fontsize() @@ -179,6 +185,20 @@ def draw_anchored_text(data: TikzData, obj: AnchoredText) -> list[str]: return content +def _is_skipped_text(data: TikzData, obj: Text, text: str) -> bool: + if text in ["", data.current_axis_title]: + return True + return _is_clipped_text3d(data, obj) + + +def _is_clipped_text3d(data: TikzData, obj: Text) -> bool: + if data.clip_3d == "none" or not isinstance(obj, Text3D) or obj.axes is None: + return False + x, y, z = _mplot3d.get_text3d_position(obj) + mask = points_inside(np.asarray([[x, y, z]], dtype=float), clip_box_from_axes(obj.axes)) + return not bool(mask[0]) + + def _get_tikz_pos(data: TikzData, obj: Text, content: list[str]) -> str: """Gets the position in tikz format.""" pos = _annotation(data, obj, content) if isinstance(obj, Annotation) else obj.get_position() @@ -186,6 +206,12 @@ def _get_tikz_pos(data: TikzData, obj: Text, content: list[str]) -> str: if isinstance(pos, str): return pos if obj.axes: + if isinstance(obj, Text3D): + x, y, z = _mplot3d.get_text3d_position(obj) + return ( + f"(axis cs:{x:{data.float_format}},{y:{data.float_format}},{z:{data.float_format}})" + ) + # Check if the text uses axes-relative coordinates (transform=ax.transAxes). # In that case, use `rel axis cs` instead of `axis cs`. transform = obj.get_transform() diff --git a/src/matplot2tikz/_tikzdata.py b/src/matplot2tikz/_tikzdata.py index c1313ec4..1b9eddb2 100644 --- a/src/matplot2tikz/_tikzdata.py +++ b/src/matplot2tikz/_tikzdata.py @@ -8,6 +8,8 @@ if TYPE_CHECKING: from matplotlib.axes import Axes + from ._clip3d import Clip3DMode + @dataclass class TikzData: @@ -22,6 +24,8 @@ class TikzData: strict: bool = False standalone: bool = False is_in_groupplot_env: bool = False + clip_3d: Clip3DMode = "none" + shader: str = "none" dpi: int = 100 font_size: float = 10.0 @@ -52,6 +56,7 @@ class TikzData: nb_keys: dict = field(default_factory=dict) current_mpl_axes: Axes | None = None + custom_colormap_id: int = 0 class Flavors(enum.Enum): @@ -101,6 +106,6 @@ def preamble(self, data: TikzData | None = None) -> str: tikzlibs = ",".join(data.tikz_libs) return self.value[3].format(pgfplotslibs=pgfplotslibs, tikzlibs=tikzlibs) - def standalone(self, code: str) -> str: + def standalone(self, code: str, data: TikzData | None = None) -> str: docenv = self.value[2] - return f"{self.preamble()}{self.start(docenv)}\n{code}\n{self.end(docenv)}" + return f"{self.preamble(data)}{self.start(docenv)}\n{code}\n{self.end(docenv)}" diff --git a/src/matplot2tikz/_util.py b/src/matplot2tikz/_util.py index 2b233373..a6053bec 100644 --- a/src/matplot2tikz/_util.py +++ b/src/matplot2tikz/_util.py @@ -100,13 +100,12 @@ def _tex_escape(text: str) -> str: # Not using \mathnormal instead since this looks odd for the latex cm font. text = _replace_mathdefault(text) text = text.replace("\N{MINUS SIGN}", r"\ensuremath{-}") - # Work around - text = text.replace("&", r"\&") - text = text.replace("_", r"\_") - text = text.replace("%", r"\%") - # split text into normaltext and inline math parts + # Split text into normaltext and inline math parts parts = _split_math(text) for i, s in enumerate(parts): if i % 2: # mathmode replacements parts[i] = rf"\(\displaystyle {s}\)" + else: + # Work around + parts[i] = s.replace("&", r"\&").replace("_", r"\_").replace("%", r"\%") return "".join(parts) diff --git a/tests/test_3d.py b/tests/test_3d.py new file mode 100644 index 00000000..6d193542 --- /dev/null +++ b/tests/test_3d.py @@ -0,0 +1,724 @@ +"""Test 3D plot export.""" + +import tempfile +from collections.abc import Callable +from pathlib import Path +from typing import TYPE_CHECKING, Literal, cast + +import matplotlib as mpl +import matplotlib.pyplot as plt +import numpy as np +import pytest +from matplotlib.figure import Figure +from mpl_toolkits.mplot3d.art3d import Poly3DCollection +from typing_extensions import NotRequired, TypedDict, Unpack + +import matplot2tikz + +from .helpers import assert_equality + +mpl.use("Agg") + +if TYPE_CHECKING: + from mpl_toolkits.mplot3d import Axes3D + +Clip3DMode = Literal["none", "hide", "clip"] +ShaderMode = Literal["none", "interp"] + + +class _TikzCodeOptions(TypedDict): + axis_width: NotRequired[str | None] + axis_height: NotRequired[str | None] + extra_axis_parameters: NotRequired[list[str] | None] + strict: NotRequired[bool] + add_axis_environment: NotRequired[bool] + standalone: NotRequired[bool] + externalize_tables: NotRequired[bool] + clip_3d: NotRequired[Clip3DMode] + shader: NotRequired[ShaderMode] + + +def plot_line_and_scatter() -> Figure: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + + theta = np.linspace(0.0, 2.5 * np.pi, 18) + radius = 0.25 + 0.07 * theta + ax.plot( + radius * np.cos(theta), + radius * np.sin(theta), + 0.18 * theta, + color="tab:red", + marker="o", + label="spiral", + ) + ax.plot( + np.linspace(-0.8, 0.8, 9), + 0.25 * np.sin(np.linspace(-2.0, 2.0, 9)), + 0.35 + 0.15 * np.cos(np.linspace(-2.0, 2.0, 9)), + color="tab:blue", + linestyle="--", + marker="^", + label="ridge", + ) + + xs = np.linspace(-0.7, 0.7, 4) + ys = np.linspace(-0.6, 0.6, 3) + xx, yy = np.meshgrid(xs, ys) + zz = 0.3 + 0.45 * xx**2 + 0.25 * np.cos(np.pi * yy) + ax.scatter( + xx.ravel(), + yy.ravel(), + zz.ravel(), + c=zz.ravel(), + s=np.linspace(12.0, 52.0, zz.size), + cmap="viridis", + marker="D", + edgecolors="black", + linewidths=0.35, + label="samples", + ) + ax.set_xlabel("X") + ax.set_ylabel("Y") + ax.set_zlabel("Z") + ax.set_xlim(-1.05, 1.05) + ax.set_ylim(-0.9, 0.9) + ax.set_zlim(0.0, 1.6) + ax.view_init(elev=28.0, azim=38.0) + ax.text(0.28, -0.72, 1.18, "3D text") + ax.legend(loc="upper left") + return fig + + +def plot_surface_and_wireframe() -> Figure: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + x = np.arange(-5, 5, 0.5) + y = np.arange(-5, 5, 0.5) + xx, yy = np.meshgrid(x, y) + zz = np.sin(np.sqrt(xx**2 + yy**2)) + ax.plot_surface( + xx, + yy, + zz, + cmap=plt.get_cmap("viridis"), + edgecolor="black", + linewidth=0.25, + alpha=0.88, + ) + ax.plot_wireframe( + xx, + yy, + zz + 0.75, + color="black", + linewidth=0.45, + rstride=2, + cstride=2, + ) + ax.contour( + xx, + yy, + zz, + levels=[-0.2, 0.0, 0.2], + colors=["navy", "darkorange", "crimson"], + linewidths=0.9, + ) + ax.contour( + xx, + yy, + zz, + levels=[-0.2, 0.0, 0.2], + zdir="z", + offset=-0.85, + cmap="viridis", + linewidths=0.6, + ) + ax.set_xlabel("X") + ax.set_ylabel("Y") + ax.set_zlabel("Z") + ax.set_xlim(-5, 5) + ax.set_ylim(-5, 5) + ax.set_zlim(-1, 1) + ax.view_init(elev=24.0, azim=-55.0) + return fig + + +def plot_bar3d() -> Figure: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + xpos, ypos = np.meshgrid([0.0, 1.1, 2.2], [0.0, 1.0]) + xpos = xpos.ravel() + ypos = ypos.ravel() + zpos = np.zeros_like(xpos) + dx = np.array([0.55, 0.75, 0.65, 0.55, 0.75, 0.65]) + dy = np.array([0.55, 0.45, 0.65, 0.75, 0.5, 0.6]) + dz = np.array([0.5, 1.15, 0.8, 1.35, 0.65, 1.55]) + colors = plt.get_cmap("cividis")((dz - dz.min()) / (dz.max() - dz.min())) + ax.bar3d( + xpos, + ypos, + zpos, + dx, + dy, + dz, + color=colors, + edgecolor="black", + linewidth=0.35, + alpha=0.82, + shade=False, + ) + ax.set_xlabel("X") + ax.set_ylabel("Y") + ax.set_zlabel("Z") + ax.set_xlim(-0.2, 3.05) + ax.set_ylim(-0.2, 1.9) + ax.set_zlim(0.0, 1.7) + ax.view_init(elev=26.0, azim=35.0) + return fig + + +def plot_quiver3d() -> Figure: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + x = np.array([0.0, 0.9, 0.0, 0.9]) + y = np.array([0.0, 0.0, 0.8, 0.8]) + z = np.array([0.0, 0.2, 0.1, 0.3]) + u = np.array([0.45, -0.2, 0.25, -0.3]) + v = np.array([0.15, 0.35, -0.25, -0.2]) + w = np.array([0.35, 0.25, 0.45, 0.2]) + ax.quiver( + x, + y, + z, + u, + v, + w, + length=0.8, + arrow_length_ratio=0.25, + color="tab:green", + linewidth=1.1, + ) + ax.set_xlabel("X") + ax.set_ylabel("Y") + ax.set_zlabel("Z") + ax.set_xlim(-0.1, 1.1) + ax.set_ylim(-0.1, 1.1) + ax.set_zlim(0.0, 0.8) + ax.view_init(elev=30.0, azim=-45.0) + return fig + + +def test_line_and_scatter() -> None: + assert_equality(plot_line_and_scatter, "test_3d_line_and_scatter_reference.tex") + + +def test_colormapped_3d_scatter_preserves_marker_and_edge() -> None: + code = _tikz_code(plot_line_and_scatter) + + assert "mark=diamond*" in code + assert "draw=black" in code + assert "line width=0.14pt" in code + + +def test_surface_and_wireframe() -> None: + assert_equality(plot_surface_and_wireframe, "test_3d_surface_and_wireframe_reference.tex") + + +def test_3d_surface_shader_option() -> None: + code = _tikz_code(plot_surface_and_wireframe, shader="interp") + + assert "shader=interp" in code + assert "patch type=bilinear" in code + assert r"point meta=\thisrow{meta}" in code + assert "x y z meta\\\\" in code + assert "patch table with point meta" not in code + assert "patch type=polygon,\nvertex count=4" not in code + + +def test_3d_axis_uses_native_label_and_tick_layout() -> None: + code = _tikz_code(plot_surface_and_wireframe) + + assert "ylabel style={rotate=-90.0}" not in code + assert "yticklabel style={anchor=center}" not in code + assert "tick pos=left" not in code + assert "tick align=outside" not in code + + +def test_3d_grid_matches_mplot3d_default() -> None: + code = _tikz_code(plot_surface_and_wireframe) + + assert "xmajorgrids" in code + assert "ymajorgrids" in code + assert "zmajorgrids" in code + + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + ax.plot([0.0, 1.0], [0.0, 1.0], [0.0, 1.0]) + ax.grid(visible=False) + code = matplot2tikz.get_tikz_code(fig, include_disclaimer=False, float_format=".8g") + plt.close("all") + + assert "xmajorgrids" not in code + assert "ymajorgrids" not in code + assert "zmajorgrids" not in code + + +def test_3d_view_uses_pgfplots_azimuth_convention() -> None: + code = _tikz_code(plot_line_and_scatter) + + assert "view={128}{28}" in code + + +def test_strict_3d_ticks_use_matplotlib_locations() -> None: + code = _tikz_code(plot_surface_and_wireframe, strict=True) + + assert "xtick={-6,-4,-2,0,2,4,6}" in code + assert "ytick={-6,-4,-2,0,2,4,6}" in code + assert "ztick={-1,-0.75,-0.5,-0.25,0,0.25,0.5,0.75,1}" in code + + +def test_3d_custom_tick_labels_are_exported_without_strict() -> None: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + ax.plot([0.0, 1.0], [0.0, 1.0], [0.0, 1.0]) + ax.set_xticks([0.0, 0.5, 1.0], ["left", "middle", "right"]) + ax.set_yticks([0.0, 1.0], ["front", "back"]) + ax.set_zticks([0.0, 1.0], ["low", "high"]) + + code = matplot2tikz.get_tikz_code(fig, include_disclaimer=False, float_format=".8g") + plt.close("all") + + assert "xtick={0,0.5,1}" in code + assert "xticklabels={left,middle,right}" in code + assert "ytick={0,1}" in code + assert "yticklabels={front,back}" in code + assert "ztick={0,1}" in code + assert "zticklabels={low,high}" in code + + +def test_strict_3d_custom_tick_locations_are_exported() -> None: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + ax.plot([0.0, 1.0], [0.0, 1.0], [0.0, 1.0]) + ax.set_xticks([0.0, 0.3, 0.9]) + ax.set_yticks([0.1, 0.4, 1.0]) + ax.set_zticks([0.2, 0.6, 1.0]) + + code = matplot2tikz.get_tikz_code( + fig, include_disclaimer=False, float_format=".8g", strict=True + ) + plt.close("all") + + assert "xtick={0,0.3,0.9}" in code + assert "ytick={0.1,0.4,1}" in code + assert "ztick={0.2,0.6,1}" in code + + +def test_bar3d() -> None: + assert_equality(plot_bar3d, "test_3d_bar3d_reference.tex", strict=True) + + +def test_bar3d_sorts_faces_in_one_patch_plot() -> None: + code = _tikz_code(plot_bar3d, strict=True) + + assert code.count("\\addplot3 [") == 1 + assert "patch table with point meta" in code + assert "colormap={matplot2tikzpoly0}" in code + assert "ztick={0,0.25,0.5,0.75,1,1.25,1.5,1.75}" in code + + +def test_poly3d_colormap_keeps_colors_aligned_after_filtering() -> None: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + collection = Poly3DCollection( + [ + np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]]), + np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]), + np.array([[0.0, 0.0, 1.0], [1.0, 0.0, 1.0], [0.0, 1.0, 1.0]]), + ], + edgecolor="black", + ) + collection.set_array(np.array([0.0, 1.0, 2.0])) + ax.add_collection3d(collection) + ax.set_xlim(0.0, 1.0) + ax.set_ylim(0.0, 1.0) + ax.set_zlim(0.0, 1.0) + + code = matplot2tikz.get_tikz_code(fig, include_disclaimer=False, float_format=".8g") + plt.close("all") + + assert "0 1 2 1\\\\" in code + assert "3 4 5 2\\\\" in code + assert "0 1 2 0\\\\" not in code + + +def test_clip3d_line_clips_to_axis_limits() -> None: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + ax.plot([-1.0, 2.0], [0.5, 0.5], [0.0, 0.0]) + ax.set_xlim(0.0, 1.0) + ax.set_ylim(0.0, 1.0) + ax.set_zlim(-1.0, 1.0) + + code = matplot2tikz.get_tikz_code( + fig, include_disclaimer=False, float_format=".8g", clip_3d="clip" + ) + plt.close("all") + + assert "0 0.5 0" in code + assert "1 0.5 0" in code + assert "-1 0.5 0" not in code + + +def test_clip3d_line_preserves_contiguous_polyline() -> None: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + ax.plot( + [-1.0, 0.0, 0.25, 0.5, 0.75, 1.0, 2.0], + [0.5] * 7, + [0.5] * 7, + ) + ax.set_xlim(0.0, 1.0) + ax.set_ylim(0.0, 1.0) + ax.set_zlim(0.0, 1.0) + + code = matplot2tikz.get_tikz_code( + fig, include_disclaimer=False, float_format=".8g", clip_3d="clip" + ) + plt.close("all") + + assert code.count("\\addplot3 [") == 1 + assert "0 0.5 0.5" in code + assert "0.25 0.5 0.5" in code + assert "1 0.5 0.5" in code + assert "-1 0.5 0.5" not in code + assert "2 0.5 0.5" not in code + + +def test_clip3d_line_preserves_disconnected_polyline_runs() -> None: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + ax.plot( + [0.0, 1.0, np.nan, 1.0, 0.0], + [0.5, 0.5, np.nan, 0.5, 0.5], + [0.5, 0.5, np.nan, 0.5, 0.5], + ) + ax.set_xlim(0.0, 1.0) + ax.set_ylim(0.0, 1.0) + ax.set_zlim(0.0, 1.0) + + code = matplot2tikz.get_tikz_code( + fig, include_disclaimer=False, float_format=".8g", clip_3d="clip" + ) + plt.close("all") + + assert code.count("\\addplot3 [") == 2 # noqa: PLR2004 + + +def test_clip3d_line_uses_log_axis_coordinates() -> None: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + ax.plot([0.1, 10.0], [0.0, 2.0], [1.0, 1.0]) + ax.set_xlim(1.0, 10.0) + ax.set_xscale("log") + ax.set_ylim(0.0, 2.0) + ax.set_zlim(0.1, 10.0) + + code = matplot2tikz.get_tikz_code( + fig, include_disclaimer=False, float_format=".8g", clip_3d="clip" + ) + plt.close("all") + + assert "1 1 1" in code + assert "10 2 1" in code + assert "0.1 0 1" not in code + + +def test_clip3d_scatter_hides_points_outside_limits() -> None: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + ax.scatter( + [0.2, 1.5, 0.8], + [0.2, 0.5, 1.8], + [0.2, 0.5, 0.8], + c=[1.0, 2.0, 3.0], + s=[10.0, 20.0, 30.0], + ) + ax.set_xlim(0.0, 1.0) + ax.set_ylim(0.0, 1.0) + ax.set_zlim(0.0, 1.0) + + code = matplot2tikz.get_tikz_code( + fig, include_disclaimer=False, float_format=".8g", clip_3d="hide" + ) + plt.close("all") + + assert "0.2 0.2 0.2 1" in code + assert "1.5 0.5 0.5 2" not in code + assert "0.8 1.8 0.8 3" not in code + + +def test_clip3d_poly_collection_clips_polygon_vertices() -> None: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + collection = Poly3DCollection( + [np.array([[-0.5, 0.2, 0.2], [0.5, 0.2, 0.2], [0.5, 0.8, 0.2]])], + facecolor="tab:red", + ) + ax.add_collection3d(collection) + ax.set_xlim(0.0, 1.0) + ax.set_ylim(0.0, 1.0) + ax.set_zlim(0.0, 1.0) + + code = matplot2tikz.get_tikz_code( + fig, include_disclaimer=False, float_format=".8g", clip_3d="clip" + ) + plt.close("all") + + assert "0 0.2 0.2" in code + assert "-0.5 0.2 0.2" not in code + + +def test_clip3d_surface_keeps_fully_inside_quad_patches() -> None: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + x = np.array([[0.0, 1.0], [0.0, 1.0]]) + y = np.array([[0.0, 0.0], [1.0, 1.0]]) + z = np.array([[0.0, 0.0], [0.0, 0.0]]) + ax.plot_surface(x, y, z, linewidth=0.0, edgecolor="none") + ax.set_xlim(0.0, 1.0) + ax.set_ylim(0.0, 1.0) + ax.set_zlim(-1.0, 1.0) + + code = matplot2tikz.get_tikz_code( + fig, include_disclaimer=False, float_format=".8g", clip_3d="clip" + ) + plt.close("all") + + assert "vertex count=4" in code + assert "vertex count=3" not in code + + +def test_clip3d_surface_keeps_clipped_quad_patches() -> None: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + collection = Poly3DCollection( + [ + np.array( + [ + [-1.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [1.0, 1.0, 0.0], + [-1.0, 1.0, 0.0], + ] + ) + ], + facecolor="tab:blue", + edgecolor="none", + ) + ax.add_collection3d(collection) + ax.set_xlim(0.0, 1.0) + ax.set_ylim(0.0, 1.0) + ax.set_zlim(-1.0, 1.0) + + code = matplot2tikz.get_tikz_code( + fig, include_disclaimer=False, float_format=".8g", clip_3d="clip" + ) + plt.close("all") + + assert "vertex count=4" in code + assert "vertex count=3" not in code + assert "0 0 0" in code + assert "-1 0 0" not in code + + +def test_clip3d_shader_triangulates_collection_when_a_clipped_polygon_breaks() -> None: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + collection = Poly3DCollection( + [ + np.array( + [ + [0.1, 0.1, 0.0], + [0.4, 0.1, 1.0], + [0.4, 0.4, 2.0], + [0.1, 0.4, 3.0], + ] + ), + np.array( + [ + [-0.5, 0.2, 0.0], + [0.8, 0.2, 2.0], + [0.8, 0.8, 4.0], + [0.2, 0.8, 6.0], + ] + ), + ], + edgecolor="none", + ) + collection.set_array(np.array([0.0, 1.0])) + collection.set_cmap(plt.get_cmap("viridis")) + ax.add_collection3d(collection) + ax.set_xlim(0.0, 1.0) + ax.set_ylim(0.0, 1.0) + ax.set_zlim(0.0, 6.0) + + code = matplot2tikz.get_tikz_code( + fig, + include_disclaimer=False, + float_format=".8g", + clip_3d="clip", + shader="interp", + ) + plt.close("all") + + assert "patch type=triangle" in code + assert "patch type=bilinear" not in code + assert "patch type=polygon" not in code + assert "shader=interp" in code + assert r"point meta=\thisrow{meta}" in code + + +def test_clip3d_quiver_hide_keeps_semantic_quiver_for_inside_arrows() -> None: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + ax.quiver( + [0.2, 1.2], + [0.2, 0.2], + [0.2, 0.2], + [0.2, 0.2], + [0.0, 0.0], + [0.0, 0.0], + ) + ax.set_xlim(0.0, 1.0) + ax.set_ylim(0.0, 1.0) + ax.set_zlim(0.0, 1.0) + + code = matplot2tikz.get_tikz_code( + fig, include_disclaimer=False, float_format=".8g", clip_3d="hide" + ) + plt.close("all") + + assert r"quiver={u=\thisrow{u}, v=\thisrow{v}, w=\thisrow{w}}" in code + assert "0.2 0.2 0.2 0.2 0 0" in code + assert "1.2 0.2 0.2 0.2 0 0" not in code + + +def test_clip3d_rejects_unknown_mode() -> None: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + ax.plot([0.0, 1.0], [0.0, 1.0], [0.0, 1.0]) + + with pytest.raises(ValueError, match="clip_3d"): + matplot2tikz.get_tikz_code( + fig, + include_disclaimer=False, + clip_3d=cast("Clip3DMode", "invalid"), + ) + plt.close("all") + + +def test_quiver3d() -> None: + assert_equality(plot_quiver3d, "test_3d_quiver_reference.tex") + + +def test_quiver3d_uses_semantic_pgfplots_quiver() -> None: + code = _tikz_code(plot_quiver3d) + + assert code.count("\\addplot3 [") == 1 + assert r"quiver={u=\thisrow{u}, v=\thisrow{v}, w=\thisrow{w}}" in code + assert "x y z u v w" in code + assert "-latex" in code + assert "mark=none" in code + + +def test_3d_axis_size_and_extra_parameters() -> None: + code = _tikz_code( + plot_line_and_scatter, + axis_width="7cm", + axis_height="5cm", + extra_axis_parameters=["name=threeDaxis"], + ) + + assert "width=7cm" in code + assert "height=5cm" in code + assert "name=threeDaxis" in code + + +def test_3d_without_axis_environment() -> None: + code = _tikz_code(plot_line_and_scatter, add_axis_environment=False) + + assert "\\begin{axis}" not in code + assert "\\end{axis}" not in code + assert "\\addplot3" in code + + +def test_3d_standalone_includes_dynamic_libraries() -> None: + code = _tikz_code(plot_surface_and_wireframe, standalone=True) + + assert "\\documentclass{standalone}" in code + assert "\\usepgfplotslibrary{patchplots}" in code + assert "\\begin{document}" in code + + +def test_3d_externalize_tables() -> None: + fig = plot_line_and_scatter() + with tempfile.TemporaryDirectory() as tmpdir: + filepath = Path(tmpdir) / "plot.tex" + matplot2tikz.save( + filepath, + figure=fig, + include_disclaimer=False, + externalize_tables=True, + float_format=".8g", + ) + + code = filepath.read_text(encoding="utf-8") + table_files = list(Path(tmpdir).glob("plot-*.dat")) + + plt.close("all") + + assert "\\addplot3" in code + assert table_files + assert "plot-000.dat" in code + + +def test_contour3d_uses_native_coordinates() -> None: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + x = np.array([0.0, 1.0, 2.0]) + y = np.array([0.0, 1.0, 2.0]) + xx, yy = np.meshgrid(x, y) + zz = xx + yy + ax.contour(xx, yy, zz, levels=[1.0, 2.0, 3.0]) + + code = matplot2tikz.get_tikz_code(fig, include_disclaimer=False, float_format=".8g") + plt.close("all") + + assert "\\addplot3" in code + assert "zmin=" in code + assert "0 1 1" in code + assert "0 2 2" in code + + +def test_contourf3d_uses_patchplots() -> None: + fig = plt.figure() + ax = cast("Axes3D", fig.add_subplot(111, projection="3d")) + x = np.array([0.0, 1.0, 2.0]) + y = np.array([0.0, 1.0, 2.0]) + xx, yy = np.meshgrid(x, y) + zz = xx + yy + ax.contourf(xx, yy, zz, levels=[1.0, 2.0, 3.0]) + + code = matplot2tikz.get_tikz_code(fig, include_disclaimer=False, float_format=".8g") + plt.close("all") + + assert "\\addplot3" in code + assert "patch table" in code + + +def _tikz_code(plot: Callable[[], Figure], **kwargs: Unpack[_TikzCodeOptions]) -> str: + fig = plot() + code = matplot2tikz.get_tikz_code(fig, include_disclaimer=False, float_format=".8g", **kwargs) + plt.close("all") + return code diff --git a/tests/test_3d_bar3d_reference.tex b/tests/test_3d_bar3d_reference.tex new file mode 100644 index 00000000..b595d3db --- /dev/null +++ b/tests/test_3d_bar3d_reference.tex @@ -0,0 +1,236 @@ +\begin{tikzpicture} + +\definecolor{darkgray176}{RGB}{176,176,176} + +\begin{axis}[ +minor xtick={}, +minor ytick={}, +minor ztick={}, +view={125}{26}, +x grid style={darkgray176}, +xlabel={X}, +xmajorgrids, +xmin=-0.2, xmax=3.05, +xtick style={color=black}, +xtick={-0.5,0,0.5,1,1.5,2,2.5,3,3.5}, +y grid style={darkgray176}, +ylabel={Y}, +ymajorgrids, +ymin=-0.2, ymax=1.9, +ytick style={color=black}, +ytick={-0.5,0,0.5,1,1.5,2}, +z grid style={darkgray176}, +zlabel={Z}, +zmajorgrids, +zmin=0, zmax=1.7, +ztick style={color=black}, +ztick={0,0.25,0.5,0.75,1,1.25,1.5,1.75} +] +\addplot3 [ +patch, +patch type=polygon, +vertex count=4, +table/row sep=\\, +z buffer=sort, +draw=black, +line width=0.14pt, +fill opacity=0.82, +colormap={matplot2tikzpoly0}{[1pt] + rgb(0pt)=(0,0.135112,0.304751); + rgb(1pt)=(0.601354,0.573743,0.465074); + rgb(2pt)=(0.298421,0.332247,0.423973); + rgb(3pt)=(0.79848,0.73058,0.392597); + rgb(4pt)=(0.130669,0.231458,0.43284); + rgb(5pt)=(0.995737,0.909344,0.217772) +}, +point meta min=0, +point meta max=5, +patch table with point meta={% +0 1 2 3 0\\ +4 5 6 7 0\\ +8 9 10 11 0\\ +12 13 14 15 0\\ +16 17 18 19 0\\ +20 21 22 23 0\\ +24 25 26 27 1\\ +28 29 30 31 1\\ +32 33 34 35 1\\ +36 37 38 39 1\\ +40 41 42 43 1\\ +44 45 46 47 1\\ +48 49 50 51 2\\ +52 53 54 55 2\\ +56 57 58 59 2\\ +60 61 62 63 2\\ +64 65 66 67 2\\ +68 69 70 71 2\\ +72 73 74 75 3\\ +76 77 78 79 3\\ +80 81 82 83 3\\ +84 85 86 87 3\\ +88 89 90 91 3\\ +92 93 94 95 3\\ +96 97 98 99 4\\ +100 101 102 103 4\\ +104 105 106 107 4\\ +108 109 110 111 4\\ +112 113 114 115 4\\ +116 117 118 119 4\\ +120 121 122 123 5\\ +124 125 126 127 5\\ +128 129 130 131 5\\ +132 133 134 135 5\\ +136 137 138 139 5\\ +140 141 142 143 5\\ +} +] +table [row sep=\\] {% +x y z\\ +0 0 0\\ +0 0.55 0\\ +0.55 0.55 0\\ +0.55 0 0\\ +0 0 0.5\\ +0.55 0 0.5\\ +0.55 0.55 0.5\\ +0 0.55 0.5\\ +0 0 0\\ +0.55 0 0\\ +0.55 0 0.5\\ +0 0 0.5\\ +0 0.55 0\\ +0 0.55 0.5\\ +0.55 0.55 0.5\\ +0.55 0.55 0\\ +0 0 0\\ +0 0 0.5\\ +0 0.55 0.5\\ +0 0.55 0\\ +0.55 0 0\\ +0.55 0.55 0\\ +0.55 0.55 0.5\\ +0.55 0 0.5\\ +1.1 0 0\\ +1.1 0.45 0\\ +1.85 0.45 0\\ +1.85 0 0\\ +1.1 0 1.15\\ +1.85 0 1.15\\ +1.85 0.45 1.15\\ +1.1 0.45 1.15\\ +1.1 0 0\\ +1.85 0 0\\ +1.85 0 1.15\\ +1.1 0 1.15\\ +1.1 0.45 0\\ +1.1 0.45 1.15\\ +1.85 0.45 1.15\\ +1.85 0.45 0\\ +1.1 0 0\\ +1.1 0 1.15\\ +1.1 0.45 1.15\\ +1.1 0.45 0\\ +1.85 0 0\\ +1.85 0.45 0\\ +1.85 0.45 1.15\\ +1.85 0 1.15\\ +2.2 0 0\\ +2.2 0.65 0\\ +2.85 0.65 0\\ +2.85 0 0\\ +2.2 0 0.8\\ +2.85 0 0.8\\ +2.85 0.65 0.8\\ +2.2 0.65 0.8\\ +2.2 0 0\\ +2.85 0 0\\ +2.85 0 0.8\\ +2.2 0 0.8\\ +2.2 0.65 0\\ +2.2 0.65 0.8\\ +2.85 0.65 0.8\\ +2.85 0.65 0\\ +2.2 0 0\\ +2.2 0 0.8\\ +2.2 0.65 0.8\\ +2.2 0.65 0\\ +2.85 0 0\\ +2.85 0.65 0\\ +2.85 0.65 0.8\\ +2.85 0 0.8\\ +0 1 0\\ +0 1.75 0\\ +0.55 1.75 0\\ +0.55 1 0\\ +0 1 1.35\\ +0.55 1 1.35\\ +0.55 1.75 1.35\\ +0 1.75 1.35\\ +0 1 0\\ +0.55 1 0\\ +0.55 1 1.35\\ +0 1 1.35\\ +0 1.75 0\\ +0 1.75 1.35\\ +0.55 1.75 1.35\\ +0.55 1.75 0\\ +0 1 0\\ +0 1 1.35\\ +0 1.75 1.35\\ +0 1.75 0\\ +0.55 1 0\\ +0.55 1.75 0\\ +0.55 1.75 1.35\\ +0.55 1 1.35\\ +1.1 1 0\\ +1.1 1.5 0\\ +1.85 1.5 0\\ +1.85 1 0\\ +1.1 1 0.65\\ +1.85 1 0.65\\ +1.85 1.5 0.65\\ +1.1 1.5 0.65\\ +1.1 1 0\\ +1.85 1 0\\ +1.85 1 0.65\\ +1.1 1 0.65\\ +1.1 1.5 0\\ +1.1 1.5 0.65\\ +1.85 1.5 0.65\\ +1.85 1.5 0\\ +1.1 1 0\\ +1.1 1 0.65\\ +1.1 1.5 0.65\\ +1.1 1.5 0\\ +1.85 1 0\\ +1.85 1.5 0\\ +1.85 1.5 0.65\\ +1.85 1 0.65\\ +2.2 1 0\\ +2.2 1.6 0\\ +2.85 1.6 0\\ +2.85 1 0\\ +2.2 1 1.55\\ +2.85 1 1.55\\ +2.85 1.6 1.55\\ +2.2 1.6 1.55\\ +2.2 1 0\\ +2.85 1 0\\ +2.85 1 1.55\\ +2.2 1 1.55\\ +2.2 1.6 0\\ +2.2 1.6 1.55\\ +2.85 1.6 1.55\\ +2.85 1.6 0\\ +2.2 1 0\\ +2.2 1 1.55\\ +2.2 1.6 1.55\\ +2.2 1.6 0\\ +2.85 1 0\\ +2.85 1.6 0\\ +2.85 1.6 1.55\\ +2.85 1 1.55\\ +}; +\end{axis} + +\end{tikzpicture} diff --git a/tests/test_3d_line_and_scatter_reference.tex b/tests/test_3d_line_and_scatter_reference.tex new file mode 100644 index 00000000..3ee466c1 --- /dev/null +++ b/tests/test_3d_line_and_scatter_reference.tex @@ -0,0 +1,105 @@ +\begin{tikzpicture} + +\definecolor{crimson2143940}{RGB}{214,39,40} +\definecolor{darkgray176}{RGB}{176,176,176} +\definecolor{lightgray204}{RGB}{204,204,204} +\definecolor{steelblue31119180}{RGB}{31,119,180} + +\begin{axis}[ +legend cell align={left}, +legend style={ + fill opacity=0.8, + draw opacity=1, + text opacity=1, + at={(0.03,0.97)}, + anchor=north west, + draw=lightgray204 +}, +view={128}{28}, +x grid style={darkgray176}, +xlabel={X}, +xmajorgrids, +xmin=-1.05, xmax=1.05, +xtick style={color=black}, +y grid style={darkgray176}, +ylabel={Y}, +ymajorgrids, +ymin=-0.9, ymax=0.9, +ytick style={color=black}, +z grid style={darkgray176}, +zlabel={Z}, +zmajorgrids, +zmin=0, zmax=1.6, +ztick style={color=black} +] +\addplot3 [ + colormap/viridis, + draw=black, + line width=0.14pt, + mark=diamond*, + only marks, + scatter, + scatter src=explicit, + scatter/@pre marker code/.append style={/tikz/mark size=\perpointmarksize}, + visualization depends on={\thisrow{sizedata} \as\perpointmarksize} +] +table [x=x, y=y, z=z, meta=colordata]{% +x y z colordata sizedata +-0.7 -0.6 0.44324575 0.44324575 1.95441 +-0.23333333 -0.6 0.24724575 0.24724575 2.230966 +0.23333333 -0.6 0.24724575 0.24724575 2.4768326 +0.7 -0.6 0.44324575 0.44324575 2.7004055 +-0.7 0 0.7705 0.7705 2.9068334 +-0.23333333 0 0.5745 0.5745 3.0995437 +0.23333333 0 0.5745 0.5745 3.2809544 +0.7 0 0.7705 0.7705 3.452847 +-0.7 0.6 0.44324575 0.44324575 3.6165789 +-0.23333333 0.6 0.24724575 0.24724575 3.7732126 +0.23333333 0.6 0.24724575 0.24724575 3.9235983 +0.7 0.6 0.44324575 0.44324575 4.0684289 +}; +\addlegendentry{samples} +\addplot3 [semithick, crimson2143940, mark=*, mark size=3, mark options={solid}] +table {% +0.25 0 0 +0.25274034 0.12584973 0.083159806 +0.18963698 0.25111994 0.16631961 +0.063764716 0.3411111 0.24947942 +-0.10381671 0.36487789 0.33263922 +-0.27736024 0.30424969 0.41579903 +-0.41405455 0.16040559 0.49895883 +-0.47434732 -0.043954752 0.58211864 +-0.43252195 -0.26780625 0.66527844 +-0.28483103 -0.4600179 0.74843825 +-0.052906608 -0.57095322 0.83159806 +0.21881823 -0.56483495 0.91475786 +0.47154614 -0.4298711 0.99791767 +0.6448262 -0.18346887 1.0810775 +0.69079313 0.12913162 1.1642373 +0.58662156 0.44299604 1.2473971 +0.3420769 0.68698303 1.3305569 +2.4486161e-16 0.79977871 1.4137167 +}; +\addlegendentry{spiral} +\addplot3 [semithick, steelblue31119180, dashed, mark=triangle*, mark size=3, mark options={solid}] +table {% +-0.8 -0.22732436 0.28757797 +-0.6 -0.24937375 0.36061058 +-0.4 -0.21036775 0.43104535 +-0.2 -0.11985638 0.48163738 +0 0 0.5 +0.2 0.11985638 0.48163738 +0.4 0.21036775 0.43104535 +0.6 0.24937375 0.36061058 +0.8 0.22732436 0.28757797 +}; +\addlegendentry{ridge} +\draw (axis cs:0.28,-0.72,1.18) node[ + scale=0.5, + anchor=base west, + text=black, + rotate=0.0 +]{3D text}; +\end{axis} + +\end{tikzpicture} diff --git a/tests/test_3d_quiver_reference.tex b/tests/test_3d_quiver_reference.tex new file mode 100644 index 00000000..25479a39 --- /dev/null +++ b/tests/test_3d_quiver_reference.tex @@ -0,0 +1,34 @@ +\begin{tikzpicture} + +\definecolor{darkgray176}{RGB}{176,176,176} +\definecolor{forestgreen4416044}{RGB}{44,160,44} + +\begin{axis}[ +view={45}{30}, +x grid style={darkgray176}, +xlabel={X}, +xmajorgrids, +xmin=-0.1, xmax=1.1, +xtick style={color=black}, +y grid style={darkgray176}, +ylabel={Y}, +ymajorgrids, +ymin=-0.1, ymax=1.1, +ytick style={color=black}, +z grid style={darkgray176}, +zlabel={Z}, +zmajorgrids, +zmin=0, zmax=0.8, +ztick style={color=black} +] +\addplot3 [draw=forestgreen4416044, line width=0.44pt, -latex, mark=none, quiver={u=\thisrow{u}, v=\thisrow{v}, w=\thisrow{w}}] +table [x=x,y=y,z=z] {% +x y z u v w +0 0 0 0.36 0.12 0.28 +0.9 0 0.2 -0.16 0.28 0.2 +0 0.8 0.1 0.2 -0.2 0.36 +0.9 0.8 0.3 -0.24 -0.16 0.16 +}; +\end{axis} + +\end{tikzpicture} diff --git a/tests/test_3d_surface_and_wireframe_reference.tex b/tests/test_3d_surface_and_wireframe_reference.tex new file mode 100644 index 00000000..fd50cba8 --- /dev/null +++ b/tests/test_3d_surface_and_wireframe_reference.tex @@ -0,0 +1,2854 @@ +\begin{tikzpicture} + +\definecolor{crimson}{RGB}{220,20,60} +\definecolor{darkcyan32144140}{RGB}{32,144,140} +\definecolor{darkgray176}{RGB}{176,176,176} +\definecolor{darkorange}{RGB}{255,140,0} +\definecolor{gold25323136}{RGB}{253,231,36} +\definecolor{indigo68184}{RGB}{68,1,84} +\definecolor{navy}{RGB}{0,0,128} + +\begin{axis}[ +colormap/viridis, +view={35}{24}, +x grid style={darkgray176}, +xlabel={X}, +xmajorgrids, +xmin=-5, xmax=5, +xtick style={color=black}, +y grid style={darkgray176}, +ylabel={Y}, +ymajorgrids, +ymin=-5, ymax=5, +ytick style={color=black}, +z grid style={darkgray176}, +zlabel={Z}, +zmajorgrids, +zmin=-1, zmax=1, +ztick style={color=black} +] +\addplot3 [ +patch, +patch type=polygon, +vertex count=4, +table/row sep=\\, +z buffer=sort, +draw=black, +ultra thin, +fill opacity=0.88, +patch table with point meta={% +0 1 2 3 0.41199627\\ +4 5 6 7 0.092542463\\ +8 9 10 11 -0.21715694\\ +12 13 14 15 -0.48333\\ +16 17 18 19 -0.68749463\\ +20 21 22 23 -0.8263122\\ +24 25 26 27 -0.90826274\\ +28 29 30 31 -0.9485692\\ +32 33 34 35 -0.96386734\\ +36 37 38 39 -0.96774984\\ +40 41 42 43 -0.96774984\\ +44 45 46 47 -0.96386734\\ +48 49 50 51 -0.9485692\\ +52 53 54 55 -0.90826274\\ +56 57 58 59 -0.8263122\\ +60 61 62 63 -0.68749463\\ +64 65 66 67 -0.48333\\ +68 69 70 71 -0.21715694\\ +72 73 74 75 0.092542463\\ +76 77 78 79 0.092542463\\ +80 81 82 83 -0.25606586\\ +84 85 86 87 -0.55483353\\ +88 89 90 91 -0.77503684\\ +92 93 94 95 -0.9082642\\ +96 97 98 99 -0.96389031\\ +100 101 102 103 -0.96324671\\ +104 105 106 107 -0.93254602\\ +108 109 110 111 -0.89646263\\ +112 113 114 115 -0.8735261\\ +116 117 118 119 -0.8735261\\ +120 121 122 123 -0.89646263\\ +124 125 126 127 -0.93254602\\ +128 129 130 131 -0.96324671\\ +132 133 134 135 -0.96389031\\ +136 137 138 139 -0.9082642\\ +140 141 142 143 -0.77503684\\ +144 145 146 147 -0.55483353\\ +148 149 150 151 -0.25606586\\ +152 153 154 155 -0.21715694\\ +156 157 158 159 -0.55483353\\ +160 161 162 163 -0.80144326\\ +164 165 166 167 -0.93736086\\ +168 169 170 171 -0.96757113\\ +172 173 174 175 -0.91616957\\ +176 177 178 179 -0.81768027\\ +180 181 182 183 -0.70801955\\ +184 185 186 187 -0.6175063\\ +188 189 190 191 -0.56704906\\ +192 193 194 195 -0.56704906\\ +196 197 198 199 -0.6175063\\ +200 201 202 203 -0.70801955\\ +204 205 206 207 -0.81768027\\ +208 209 210 211 -0.91616957\\ +212 213 214 215 -0.96757113\\ +216 217 218 219 -0.93736086\\ +220 221 222 223 -0.80144326\\ +224 225 226 227 -0.55483353\\ +228 229 230 231 -0.48333\\ +232 233 234 235 -0.77503684\\ +236 237 238 239 -0.93736086\\ +240 241 242 243 -0.96328094\\ +244 245 246 247 -0.87363262\\ +248 249 250 251 -0.70809505\\ +252 253 254 255 -0.51331544\\ +256 257 258 259 -0.33193732\\ +260 261 262 263 -0.19563651\\ +264 265 266 267 -0.12336256\\ +268 269 270 271 -0.12336256\\ +272 273 274 275 -0.19563651\\ +276 277 278 279 -0.33193732\\ +280 281 282 283 -0.51331544\\ +284 285 286 287 -0.70809505\\ +288 289 290 291 -0.87363262\\ +292 293 294 295 -0.96328094\\ +296 297 298 299 -0.93736086\\ +300 301 302 303 -0.77503684\\ +304 305 306 307 -0.68749463\\ +308 309 310 311 -0.9082642\\ +312 313 314 315 -0.96757113\\ +316 317 318 319 -0.87363262\\ +320 321 322 323 -0.66461695\\ +324 325 326 327 -0.39568336\\ +328 329 330 331 -0.12353598\\ +332 333 334 335 0.10666798\\ +336 337 338 339 0.26788122\\ +340 341 342 343 0.34943162\\ +344 345 346 347 0.34943162\\ +348 349 350 351 0.26788122\\ +352 353 354 355 0.10666798\\ +356 357 358 359 -0.12353598\\ +360 361 362 363 -0.39568336\\ +364 365 366 367 -0.66461695\\ +368 369 370 371 -0.87363262\\ +372 373 374 375 -0.96757113\\ +376 377 378 379 -0.9082642\\ +380 381 382 383 -0.8263122\\ +384 385 386 387 -0.96389031\\ +388 389 390 391 -0.91616957\\ +392 393 394 395 -0.70809505\\ +396 397 398 399 -0.39568336\\ +400 401 402 403 -0.048906516\\ +404 405 406 407 0.26770129\\ +408 409 410 411 0.51087254\\ +412 413 414 415 0.6652226\\ +416 417 418 419 0.73695918\\ +420 421 422 423 0.73695918\\ +424 425 426 427 0.6652226\\ +428 429 430 431 0.51087254\\ +432 433 434 435 0.26770129\\ +436 437 438 439 -0.048906516\\ +440 441 442 443 -0.39568336\\ +444 445 446 447 -0.70809505\\ +448 449 450 451 -0.91616957\\ +452 453 454 455 -0.96389031\\ +456 457 458 459 -0.90826274\\ +460 461 462 463 -0.96324671\\ +464 465 466 467 -0.81768027\\ +468 469 470 471 -0.51331544\\ +472 473 474 475 -0.12353598\\ +476 477 478 479 0.26770129\\ +480 481 482 483 0.58931663\\ +484 485 486 487 0.80267126\\ +488 489 490 491 0.91047137\\ +492 493 494 495 0.94717888\\ +496 497 498 499 0.94717888\\ +500 501 502 503 0.91047137\\ +504 505 506 507 0.80267126\\ +508 509 510 511 0.58931663\\ +512 513 514 515 0.26770129\\ +516 517 518 519 -0.12353598\\ +520 521 522 523 -0.51331544\\ +524 525 526 527 -0.81768027\\ +528 529 530 531 -0.96324671\\ +532 533 534 535 -0.9485692\\ +536 537 538 539 -0.93254602\\ +540 541 542 543 -0.70801955\\ +544 545 546 547 -0.33193732\\ +548 549 550 551 0.10666798\\ +552 553 554 555 0.51087254\\ +556 557 558 559 0.80267126\\ +560 561 562 563 0.94661074\\ +564 565 566 567 0.96504196\\ +568 569 570 571 0.93453866\\ +572 573 574 575 0.93453866\\ +576 577 578 579 0.96504196\\ +580 581 582 583 0.94661074\\ +584 585 586 587 0.80267126\\ +588 589 590 591 0.51087254\\ +592 593 594 595 0.10666798\\ +596 597 598 599 -0.33193732\\ +600 601 602 603 -0.70801955\\ +604 605 606 607 -0.93254602\\ +608 609 610 611 -0.96386734\\ +612 613 614 615 -0.89646263\\ +616 617 618 619 -0.6175063\\ +620 621 622 623 -0.19563651\\ +624 625 626 627 0.26788122\\ +628 629 630 631 0.6652226\\ +632 633 634 635 0.91047137\\ +636 637 638 639 0.96504196\\ +640 641 642 643 0.85897179\\ +644 645 646 647 0.7174439\\ +648 649 650 651 0.7174439\\ +652 653 654 655 0.85897179\\ +656 657 658 659 0.96504196\\ +660 661 662 663 0.91047137\\ +664 665 666 667 0.6652226\\ +668 669 670 671 0.26788122\\ +672 673 674 675 -0.19563651\\ +676 677 678 679 -0.6175063\\ +680 681 682 683 -0.89646263\\ +684 685 686 687 -0.96774984\\ +688 689 690 691 -0.8735261\\ +692 693 694 695 -0.56704906\\ +696 697 698 699 -0.12336256\\ +700 701 702 703 0.34943162\\ +704 705 706 707 0.73695918\\ +708 709 710 711 0.94717888\\ +712 713 714 715 0.93453866\\ +716 717 718 719 0.7174439\\ +720 721 722 723 0.402122\\ +724 725 726 727 0.402122\\ +728 729 730 731 0.7174439\\ +732 733 734 735 0.93453866\\ +736 737 738 739 0.94717888\\ +740 741 742 743 0.73695918\\ +744 745 746 747 0.34943162\\ +748 749 750 751 -0.12336256\\ +752 753 754 755 -0.56704906\\ +756 757 758 759 -0.8735261\\ +760 761 762 763 -0.96774984\\ +764 765 766 767 -0.8735261\\ +768 769 770 771 -0.56704906\\ +772 773 774 775 -0.12336256\\ +776 777 778 779 0.34943162\\ +780 781 782 783 0.73695918\\ +784 785 786 787 0.94717888\\ +788 789 790 791 0.93453866\\ +792 793 794 795 0.7174439\\ +796 797 798 799 0.402122\\ +800 801 802 803 0.402122\\ +804 805 806 807 0.7174439\\ +808 809 810 811 0.93453866\\ +812 813 814 815 0.94717888\\ +816 817 818 819 0.73695918\\ +820 821 822 823 0.34943162\\ +824 825 826 827 -0.12336256\\ +828 829 830 831 -0.56704906\\ +832 833 834 835 -0.8735261\\ +836 837 838 839 -0.96386734\\ +840 841 842 843 -0.89646263\\ +844 845 846 847 -0.6175063\\ +848 849 850 851 -0.19563651\\ +852 853 854 855 0.26788122\\ +856 857 858 859 0.6652226\\ +860 861 862 863 0.91047137\\ +864 865 866 867 0.96504196\\ +868 869 870 871 0.85897179\\ +872 873 874 875 0.7174439\\ +876 877 878 879 0.7174439\\ +880 881 882 883 0.85897179\\ +884 885 886 887 0.96504196\\ +888 889 890 891 0.91047137\\ +892 893 894 895 0.6652226\\ +896 897 898 899 0.26788122\\ +900 901 902 903 -0.19563651\\ +904 905 906 907 -0.6175063\\ +908 909 910 911 -0.89646263\\ +912 913 914 915 -0.9485692\\ +916 917 918 919 -0.93254602\\ +920 921 922 923 -0.70801955\\ +924 925 926 927 -0.33193732\\ +928 929 930 931 0.10666798\\ +932 933 934 935 0.51087254\\ +936 937 938 939 0.80267126\\ +940 941 942 943 0.94661074\\ +944 945 946 947 0.96504196\\ +948 949 950 951 0.93453866\\ +952 953 954 955 0.93453866\\ +956 957 958 959 0.96504196\\ +960 961 962 963 0.94661074\\ +964 965 966 967 0.80267126\\ +968 969 970 971 0.51087254\\ +972 973 974 975 0.10666798\\ +976 977 978 979 -0.33193732\\ +980 981 982 983 -0.70801955\\ +984 985 986 987 -0.93254602\\ +988 989 990 991 -0.90826274\\ +992 993 994 995 -0.96324671\\ +996 997 998 999 -0.81768027\\ +1000 1001 1002 1003 -0.51331544\\ +1004 1005 1006 1007 -0.12353598\\ +1008 1009 1010 1011 0.26770129\\ +1012 1013 1014 1015 0.58931663\\ +1016 1017 1018 1019 0.80267126\\ +1020 1021 1022 1023 0.91047137\\ +1024 1025 1026 1027 0.94717888\\ +1028 1029 1030 1031 0.94717888\\ +1032 1033 1034 1035 0.91047137\\ +1036 1037 1038 1039 0.80267126\\ +1040 1041 1042 1043 0.58931663\\ +1044 1045 1046 1047 0.26770129\\ +1048 1049 1050 1051 -0.12353598\\ +1052 1053 1054 1055 -0.51331544\\ +1056 1057 1058 1059 -0.81768027\\ +1060 1061 1062 1063 -0.96324671\\ +1064 1065 1066 1067 -0.8263122\\ +1068 1069 1070 1071 -0.96389031\\ +1072 1073 1074 1075 -0.91616957\\ +1076 1077 1078 1079 -0.70809505\\ +1080 1081 1082 1083 -0.39568336\\ +1084 1085 1086 1087 -0.048906516\\ +1088 1089 1090 1091 0.26770129\\ +1092 1093 1094 1095 0.51087254\\ +1096 1097 1098 1099 0.6652226\\ +1100 1101 1102 1103 0.73695918\\ +1104 1105 1106 1107 0.73695918\\ +1108 1109 1110 1111 0.6652226\\ +1112 1113 1114 1115 0.51087254\\ +1116 1117 1118 1119 0.26770129\\ +1120 1121 1122 1123 -0.048906516\\ +1124 1125 1126 1127 -0.39568336\\ +1128 1129 1130 1131 -0.70809505\\ +1132 1133 1134 1135 -0.91616957\\ +1136 1137 1138 1139 -0.96389031\\ +1140 1141 1142 1143 -0.68749463\\ +1144 1145 1146 1147 -0.9082642\\ +1148 1149 1150 1151 -0.96757113\\ +1152 1153 1154 1155 -0.87363262\\ +1156 1157 1158 1159 -0.66461695\\ +1160 1161 1162 1163 -0.39568336\\ +1164 1165 1166 1167 -0.12353598\\ +1168 1169 1170 1171 0.10666798\\ +1172 1173 1174 1175 0.26788122\\ +1176 1177 1178 1179 0.34943162\\ +1180 1181 1182 1183 0.34943162\\ +1184 1185 1186 1187 0.26788122\\ +1188 1189 1190 1191 0.10666798\\ +1192 1193 1194 1195 -0.12353598\\ +1196 1197 1198 1199 -0.39568336\\ +1200 1201 1202 1203 -0.66461695\\ +1204 1205 1206 1207 -0.87363262\\ +1208 1209 1210 1211 -0.96757113\\ +1212 1213 1214 1215 -0.9082642\\ +1216 1217 1218 1219 -0.48333\\ +1220 1221 1222 1223 -0.77503684\\ +1224 1225 1226 1227 -0.93736086\\ +1228 1229 1230 1231 -0.96328094\\ +1232 1233 1234 1235 -0.87363262\\ +1236 1237 1238 1239 -0.70809505\\ +1240 1241 1242 1243 -0.51331544\\ +1244 1245 1246 1247 -0.33193732\\ +1248 1249 1250 1251 -0.19563651\\ +1252 1253 1254 1255 -0.12336256\\ +1256 1257 1258 1259 -0.12336256\\ +1260 1261 1262 1263 -0.19563651\\ +1264 1265 1266 1267 -0.33193732\\ +1268 1269 1270 1271 -0.51331544\\ +1272 1273 1274 1275 -0.70809505\\ +1276 1277 1278 1279 -0.87363262\\ +1280 1281 1282 1283 -0.96328094\\ +1284 1285 1286 1287 -0.93736086\\ +1288 1289 1290 1291 -0.77503684\\ +1292 1293 1294 1295 -0.21715694\\ +1296 1297 1298 1299 -0.55483353\\ +1300 1301 1302 1303 -0.80144326\\ +1304 1305 1306 1307 -0.93736086\\ +1308 1309 1310 1311 -0.96757113\\ +1312 1313 1314 1315 -0.91616957\\ +1316 1317 1318 1319 -0.81768027\\ +1320 1321 1322 1323 -0.70801955\\ +1324 1325 1326 1327 -0.6175063\\ +1328 1329 1330 1331 -0.56704906\\ +1332 1333 1334 1335 -0.56704906\\ +1336 1337 1338 1339 -0.6175063\\ +1340 1341 1342 1343 -0.70801955\\ +1344 1345 1346 1347 -0.81768027\\ +1348 1349 1350 1351 -0.91616957\\ +1352 1353 1354 1355 -0.96757113\\ +1356 1357 1358 1359 -0.93736086\\ +1360 1361 1362 1363 -0.80144326\\ +1364 1365 1366 1367 -0.55483353\\ +1368 1369 1370 1371 0.092542463\\ +1372 1373 1374 1375 -0.25606586\\ +1376 1377 1378 1379 -0.55483353\\ +1380 1381 1382 1383 -0.77503684\\ +1384 1385 1386 1387 -0.9082642\\ +1388 1389 1390 1391 -0.96389031\\ +1392 1393 1394 1395 -0.96324671\\ +1396 1397 1398 1399 -0.93254602\\ +1400 1401 1402 1403 -0.89646263\\ +1404 1405 1406 1407 -0.8735261\\ +1408 1409 1410 1411 -0.8735261\\ +1412 1413 1414 1415 -0.89646263\\ +1416 1417 1418 1419 -0.93254602\\ +1420 1421 1422 1423 -0.96324671\\ +1424 1425 1426 1427 -0.96389031\\ +1428 1429 1430 1431 -0.9082642\\ +1432 1433 1434 1435 -0.77503684\\ +1436 1437 1438 1439 -0.55483353\\ +1440 1441 1442 1443 -0.25606586\\ +} +] +table [row sep=\\] {% +x y z\\ +-5 -5 0.70886129\\ +-4.5 -5 0.42921793\\ +-4.5 -4.5 0.080687912\\ +-5 -4.5 0.42921793\\ +-4.5 -5 0.42921793\\ +-4 -5 0.11965158\\ +-4 -4.5 -0.25938757\\ +-4.5 -4.5 0.080687912\\ +-4 -5 0.11965158\\ +-3.5 -5 -0.17893857\\ +-3.5 -4.5 -0.54995318\\ +-4 -4.5 -0.25938757\\ +-3.5 -5 -0.17893857\\ +-3 -5 -0.43697552\\ +-3 -4.5 -0.76745273\\ +-3.5 -4.5 -0.54995318\\ +-3 -5 -0.43697552\\ +-2.5 -5 -0.63885987\\ +-2.5 -4.5 -0.9066904\\ +-3 -4.5 -0.76745273\\ +-2.5 -5 -0.63885987\\ +-2 -5 -0.7820949\\ +-2 -4.5 -0.97760364\\ +-2.5 -4.5 -0.9066904\\ +-2 -5 -0.7820949\\ +-1.5 -5 -0.87383376\\ +-1.5 -4.5 -0.99951869\\ +-2 -4.5 -0.97760364\\ +-1.5 -5 -0.87383376\\ +-1 -5 -0.92618484\\ +-1 -4.5 -0.99473952\\ +-1.5 -4.5 -0.99951869\\ +-1 -5 -0.92618484\\ +-0.5 -5 -0.95155293\\ +-0.5 -4.5 -0.98299205\\ +-1 -4.5 -0.99473952\\ +-0.5 -5 -0.95155293\\ +0 -5 -0.95892427\\ +0 -4.5 -0.97753012\\ +-0.5 -4.5 -0.98299205\\ +0 -5 -0.95892427\\ +0.5 -5 -0.95155293\\ +0.5 -4.5 -0.98299205\\ +0 -4.5 -0.97753012\\ +0.5 -5 -0.95155293\\ +1 -5 -0.92618484\\ +1 -4.5 -0.99473952\\ +0.5 -4.5 -0.98299205\\ +1 -5 -0.92618484\\ +1.5 -5 -0.87383376\\ +1.5 -4.5 -0.99951869\\ +1 -4.5 -0.99473952\\ +1.5 -5 -0.87383376\\ +2 -5 -0.7820949\\ +2 -4.5 -0.97760364\\ +1.5 -4.5 -0.99951869\\ +2 -5 -0.7820949\\ +2.5 -5 -0.63885987\\ +2.5 -4.5 -0.9066904\\ +2 -4.5 -0.97760364\\ +2.5 -5 -0.63885987\\ +3 -5 -0.43697552\\ +3 -4.5 -0.76745273\\ +2.5 -4.5 -0.9066904\\ +3 -5 -0.43697552\\ +3.5 -5 -0.17893857\\ +3.5 -4.5 -0.54995318\\ +3 -4.5 -0.76745273\\ +3.5 -5 -0.17893857\\ +4 -5 0.11965158\\ +4 -4.5 -0.25938757\\ +3.5 -4.5 -0.54995318\\ +4 -5 0.11965158\\ +4.5 -5 0.42921793\\ +4.5 -4.5 0.080687912\\ +4 -4.5 -0.25938757\\ +-5 -4.5 0.42921793\\ +-4.5 -4.5 0.080687912\\ +-4.5 -4 -0.25938757\\ +-5 -4 0.11965158\\ +-4.5 -4.5 0.080687912\\ +-4 -4.5 -0.25938757\\ +-4 -4 -0.58617619\\ +-4.5 -4 -0.25938757\\ +-4 -4.5 -0.25938757\\ +-3.5 -4.5 -0.54995318\\ +-3.5 -4 -0.82381719\\ +-4 -4 -0.58617619\\ +-3.5 -4.5 -0.54995318\\ +-3 -4.5 -0.76745273\\ +-3 -4 -0.95892427\\ +-3.5 -4 -0.82381719\\ +-3 -4.5 -0.76745273\\ +-2.5 -4.5 -0.9066904\\ +-2.5 -4 -0.99998941\\ +-3 -4 -0.95892427\\ +-2.5 -4.5 -0.9066904\\ +-2 -4.5 -0.97760364\\ +-2 -4 -0.9712778\\ +-2.5 -4 -0.99998941\\ +-2 -4.5 -0.97760364\\ +-1.5 -4.5 -0.99951869\\ +-1.5 -4 -0.90458671\\ +-2 -4 -0.9712778\\ +-1.5 -4.5 -0.99951869\\ +-1 -4.5 -0.99473952\\ +-1 -4 -0.83133918\\ +-1.5 -4 -0.90458671\\ +-1 -4.5 -0.99473952\\ +-0.5 -4.5 -0.98299205\\ +-0.5 -4 -0.77677976\\ +-1 -4 -0.83133918\\ +-0.5 -4.5 -0.98299205\\ +0 -4.5 -0.97753012\\ +0 -4 -0.7568025\\ +-0.5 -4 -0.77677976\\ +0 -4.5 -0.97753012\\ +0.5 -4.5 -0.98299205\\ +0.5 -4 -0.77677976\\ +0 -4 -0.7568025\\ +0.5 -4.5 -0.98299205\\ +1 -4.5 -0.99473952\\ +1 -4 -0.83133918\\ +0.5 -4 -0.77677976\\ +1 -4.5 -0.99473952\\ +1.5 -4.5 -0.99951869\\ +1.5 -4 -0.90458671\\ +1 -4 -0.83133918\\ +1.5 -4.5 -0.99951869\\ +2 -4.5 -0.97760364\\ +2 -4 -0.9712778\\ +1.5 -4 -0.90458671\\ +2 -4.5 -0.97760364\\ +2.5 -4.5 -0.9066904\\ +2.5 -4 -0.99998941\\ +2 -4 -0.9712778\\ +2.5 -4.5 -0.9066904\\ +3 -4.5 -0.76745273\\ +3 -4 -0.95892427\\ +2.5 -4 -0.99998941\\ +3 -4.5 -0.76745273\\ +3.5 -4.5 -0.54995318\\ +3.5 -4 -0.82381719\\ +3 -4 -0.95892427\\ +3.5 -4.5 -0.54995318\\ +4 -4.5 -0.25938757\\ +4 -4 -0.58617619\\ +3.5 -4 -0.82381719\\ +4 -4.5 -0.25938757\\ +4.5 -4.5 0.080687912\\ +4.5 -4 -0.25938757\\ +4 -4 -0.58617619\\ +-5 -4 0.11965158\\ +-4.5 -4 -0.25938757\\ +-4.5 -3.5 -0.54995318\\ +-5 -3.5 -0.17893857\\ +-4.5 -4 -0.25938757\\ +-4 -4 -0.58617619\\ +-4 -3.5 -0.82381719\\ +-4.5 -3.5 -0.54995318\\ +-4 -4 -0.58617619\\ +-3.5 -4 -0.82381719\\ +-3.5 -3.5 -0.97196248\\ +-4 -3.5 -0.82381719\\ +-3.5 -4 -0.82381719\\ +-3 -4 -0.95892427\\ +-3 -3.5 -0.99473952\\ +-3.5 -3.5 -0.97196248\\ +-3 -4 -0.95892427\\ +-2.5 -4 -0.99998941\\ +-2.5 -3.5 -0.9166313\\ +-3 -3.5 -0.99473952\\ +-2.5 -4 -0.99998941\\ +-2 -4 -0.9712778\\ +-2 -3.5 -0.77677976\\ +-2.5 -3.5 -0.9166313\\ +-2 -4 -0.9712778\\ +-1.5 -4 -0.90458671\\ +-1.5 -3.5 -0.61807681\\ +-2 -3.5 -0.77677976\\ +-1.5 -4 -0.90458671\\ +-1 -4 -0.83133918\\ +-1 -3.5 -0.47807551\\ +-1.5 -3.5 -0.61807681\\ +-1 -4 -0.83133918\\ +-0.5 -4 -0.77677976\\ +-0.5 -3.5 -0.38383075\\ +-1 -3.5 -0.47807551\\ +-0.5 -4 -0.77677976\\ +0 -4 -0.7568025\\ +0 -3.5 -0.35078323\\ +-0.5 -3.5 -0.38383075\\ +0 -4 -0.7568025\\ +0.5 -4 -0.77677976\\ +0.5 -3.5 -0.38383075\\ +0 -3.5 -0.35078323\\ +0.5 -4 -0.77677976\\ +1 -4 -0.83133918\\ +1 -3.5 -0.47807551\\ +0.5 -3.5 -0.38383075\\ +1 -4 -0.83133918\\ +1.5 -4 -0.90458671\\ +1.5 -3.5 -0.61807681\\ +1 -3.5 -0.47807551\\ +1.5 -4 -0.90458671\\ +2 -4 -0.9712778\\ +2 -3.5 -0.77677976\\ +1.5 -3.5 -0.61807681\\ +2 -4 -0.9712778\\ +2.5 -4 -0.99998941\\ +2.5 -3.5 -0.9166313\\ +2 -3.5 -0.77677976\\ +2.5 -4 -0.99998941\\ +3 -4 -0.95892427\\ +3 -3.5 -0.99473952\\ +2.5 -3.5 -0.9166313\\ +3 -4 -0.95892427\\ +3.5 -4 -0.82381719\\ +3.5 -3.5 -0.97196248\\ +3 -3.5 -0.99473952\\ +3.5 -4 -0.82381719\\ +4 -4 -0.58617619\\ +4 -3.5 -0.82381719\\ +3.5 -3.5 -0.97196248\\ +4 -4 -0.58617619\\ +4.5 -4 -0.25938757\\ +4.5 -3.5 -0.54995318\\ +4 -3.5 -0.82381719\\ +-5 -3.5 -0.17893857\\ +-4.5 -3.5 -0.54995318\\ +-4.5 -3 -0.76745273\\ +-5 -3 -0.43697552\\ +-4.5 -3.5 -0.54995318\\ +-4 -3.5 -0.82381719\\ +-4 -3 -0.95892427\\ +-4.5 -3 -0.76745273\\ +-4 -3.5 -0.82381719\\ +-3.5 -3.5 -0.97196248\\ +-3.5 -3 -0.99473952\\ +-4 -3 -0.95892427\\ +-3.5 -3.5 -0.97196248\\ +-3 -3.5 -0.99473952\\ +-3 -3 -0.89168225\\ +-3.5 -3 -0.99473952\\ +-3 -3.5 -0.99473952\\ +-2.5 -3.5 -0.9166313\\ +-2.5 -3 -0.6914774\\ +-3 -3 -0.89168225\\ +-2.5 -3.5 -0.9166313\\ +-2 -3.5 -0.77677976\\ +-2 -3 -0.44749175\\ +-2.5 -3 -0.6914774\\ +-2 -3.5 -0.77677976\\ +-1.5 -3.5 -0.61807681\\ +-1.5 -3 -0.21091343\\ +-2 -3 -0.44749175\\ +-1.5 -3.5 -0.61807681\\ +-1 -3.5 -0.47807551\\ +-1 -3 -0.020683532\\ +-1.5 -3 -0.21091343\\ +-1 -3.5 -0.47807551\\ +-0.5 -3.5 -0.38383075\\ +-0.5 -3 0.10004375\\ +-1 -3 -0.020683532\\ +-0.5 -3.5 -0.38383075\\ +0 -3.5 -0.35078323\\ +0 -3 0.14112001\\ +-0.5 -3 0.10004375\\ +0 -3.5 -0.35078323\\ +0.5 -3.5 -0.38383075\\ +0.5 -3 0.10004375\\ +0 -3 0.14112001\\ +0.5 -3.5 -0.38383075\\ +1 -3.5 -0.47807551\\ +1 -3 -0.020683532\\ +0.5 -3 0.10004375\\ +1 -3.5 -0.47807551\\ +1.5 -3.5 -0.61807681\\ +1.5 -3 -0.21091343\\ +1 -3 -0.020683532\\ +1.5 -3.5 -0.61807681\\ +2 -3.5 -0.77677976\\ +2 -3 -0.44749175\\ +1.5 -3 -0.21091343\\ +2 -3.5 -0.77677976\\ +2.5 -3.5 -0.9166313\\ +2.5 -3 -0.6914774\\ +2 -3 -0.44749175\\ +2.5 -3.5 -0.9166313\\ +3 -3.5 -0.99473952\\ +3 -3 -0.89168225\\ +2.5 -3 -0.6914774\\ +3 -3.5 -0.99473952\\ +3.5 -3.5 -0.97196248\\ +3.5 -3 -0.99473952\\ +3 -3 -0.89168225\\ +3.5 -3.5 -0.97196248\\ +4 -3.5 -0.82381719\\ +4 -3 -0.95892427\\ +3.5 -3 -0.99473952\\ +4 -3.5 -0.82381719\\ +4.5 -3.5 -0.54995318\\ +4.5 -3 -0.76745273\\ +4 -3 -0.95892427\\ +-5 -3 -0.43697552\\ +-4.5 -3 -0.76745273\\ +-4.5 -2.5 -0.9066904\\ +-5 -2.5 -0.63885987\\ +-4.5 -3 -0.76745273\\ +-4 -3 -0.95892427\\ +-4 -2.5 -0.99998941\\ +-4.5 -2.5 -0.9066904\\ +-4 -3 -0.95892427\\ +-3.5 -3 -0.99473952\\ +-3.5 -2.5 -0.9166313\\ +-4 -2.5 -0.99998941\\ +-3.5 -3 -0.99473952\\ +-3 -3 -0.89168225\\ +-3 -2.5 -0.6914774\\ +-3.5 -2.5 -0.9166313\\ +-3 -3 -0.89168225\\ +-2.5 -3 -0.6914774\\ +-2.5 -2.5 -0.38383075\\ +-3 -2.5 -0.6914774\\ +-2.5 -3 -0.6914774\\ +-2 -3 -0.44749175\\ +-2 -2.5 -0.059933527\\ +-2.5 -2.5 -0.38383075\\ +-2 -3 -0.44749175\\ +-1.5 -3 -0.21091343\\ +-1.5 -2.5 0.22419478\\ +-2 -2.5 -0.059933527\\ +-1.5 -3 -0.21091343\\ +-1 -3 -0.020683532\\ +-1 -2.5 0.4340741\\ +-1.5 -2.5 0.22419478\\ +-1 -3 -0.020683532\\ +-0.5 -3 0.10004375\\ +-0.5 -2.5 0.55809058\\ +-1 -2.5 0.4340741\\ +-0.5 -3 0.10004375\\ +0 -3 0.14112001\\ +0 -2.5 0.59847214\\ +-0.5 -2.5 0.55809058\\ +0 -3 0.14112001\\ +0.5 -3 0.10004375\\ +0.5 -2.5 0.55809058\\ +0 -2.5 0.59847214\\ +0.5 -3 0.10004375\\ +1 -3 -0.020683532\\ +1 -2.5 0.4340741\\ +0.5 -2.5 0.55809058\\ +1 -3 -0.020683532\\ +1.5 -3 -0.21091343\\ +1.5 -2.5 0.22419478\\ +1 -2.5 0.4340741\\ +1.5 -3 -0.21091343\\ +2 -3 -0.44749175\\ +2 -2.5 -0.059933527\\ +1.5 -2.5 0.22419478\\ +2 -3 -0.44749175\\ +2.5 -3 -0.6914774\\ +2.5 -2.5 -0.38383075\\ +2 -2.5 -0.059933527\\ +2.5 -3 -0.6914774\\ +3 -3 -0.89168225\\ +3 -2.5 -0.6914774\\ +2.5 -2.5 -0.38383075\\ +3 -3 -0.89168225\\ +3.5 -3 -0.99473952\\ +3.5 -2.5 -0.9166313\\ +3 -2.5 -0.6914774\\ +3.5 -3 -0.99473952\\ +4 -3 -0.95892427\\ +4 -2.5 -0.99998941\\ +3.5 -2.5 -0.9166313\\ +4 -3 -0.95892427\\ +4.5 -3 -0.76745273\\ +4.5 -2.5 -0.9066904\\ +4 -2.5 -0.99998941\\ +-5 -2.5 -0.63885987\\ +-4.5 -2.5 -0.9066904\\ +-4.5 -2 -0.97760364\\ +-5 -2 -0.7820949\\ +-4.5 -2.5 -0.9066904\\ +-4 -2.5 -0.99998941\\ +-4 -2 -0.9712778\\ +-4.5 -2 -0.97760364\\ +-4 -2.5 -0.99998941\\ +-3.5 -2.5 -0.9166313\\ +-3.5 -2 -0.77677976\\ +-4 -2 -0.9712778\\ +-3.5 -2.5 -0.9166313\\ +-3 -2.5 -0.6914774\\ +-3 -2 -0.44749175\\ +-3.5 -2 -0.77677976\\ +-3 -2.5 -0.6914774\\ +-2.5 -2.5 -0.38383075\\ +-2.5 -2 -0.059933527\\ +-3 -2 -0.44749175\\ +-2.5 -2.5 -0.38383075\\ +-2 -2.5 -0.059933527\\ +-2 -2 0.30807174\\ +-2.5 -2 -0.059933527\\ +-2 -2.5 -0.059933527\\ +-1.5 -2.5 0.22419478\\ +-1.5 -2 0.59847214\\ +-2 -2 0.30807174\\ +-1.5 -2.5 0.22419478\\ +-1 -2.5 0.4340741\\ +-1 -2 0.78674913\\ +-1.5 -2 0.59847214\\ +-1 -2.5 0.4340741\\ +-0.5 -2.5 0.55809058\\ +-0.5 -2 0.88197658\\ +-1 -2 0.78674913\\ +-0.5 -2.5 0.55809058\\ +0 -2.5 0.59847214\\ +0 -2 0.90929743\\ +-0.5 -2 0.88197658\\ +0 -2.5 0.59847214\\ +0.5 -2.5 0.55809058\\ +0.5 -2 0.88197658\\ +0 -2 0.90929743\\ +0.5 -2.5 0.55809058\\ +1 -2.5 0.4340741\\ +1 -2 0.78674913\\ +0.5 -2 0.88197658\\ +1 -2.5 0.4340741\\ +1.5 -2.5 0.22419478\\ +1.5 -2 0.59847214\\ +1 -2 0.78674913\\ +1.5 -2.5 0.22419478\\ +2 -2.5 -0.059933527\\ +2 -2 0.30807174\\ +1.5 -2 0.59847214\\ +2 -2.5 -0.059933527\\ +2.5 -2.5 -0.38383075\\ +2.5 -2 -0.059933527\\ +2 -2 0.30807174\\ +2.5 -2.5 -0.38383075\\ +3 -2.5 -0.6914774\\ +3 -2 -0.44749175\\ +2.5 -2 -0.059933527\\ +3 -2.5 -0.6914774\\ +3.5 -2.5 -0.9166313\\ +3.5 -2 -0.77677976\\ +3 -2 -0.44749175\\ +3.5 -2.5 -0.9166313\\ +4 -2.5 -0.99998941\\ +4 -2 -0.9712778\\ +3.5 -2 -0.77677976\\ +4 -2.5 -0.99998941\\ +4.5 -2.5 -0.9066904\\ +4.5 -2 -0.97760364\\ +4 -2 -0.9712778\\ +-5 -2 -0.7820949\\ +-4.5 -2 -0.97760364\\ +-4.5 -1.5 -0.99951869\\ +-5 -1.5 -0.87383376\\ +-4.5 -2 -0.97760364\\ +-4 -2 -0.9712778\\ +-4 -1.5 -0.90458671\\ +-4.5 -1.5 -0.99951869\\ +-4 -2 -0.9712778\\ +-3.5 -2 -0.77677976\\ +-3.5 -1.5 -0.61807681\\ +-4 -1.5 -0.90458671\\ +-3.5 -2 -0.77677976\\ +-3 -2 -0.44749175\\ +-3 -1.5 -0.21091343\\ +-3.5 -1.5 -0.61807681\\ +-3 -2 -0.44749175\\ +-2.5 -2 -0.059933527\\ +-2.5 -1.5 0.22419478\\ +-3 -1.5 -0.21091343\\ +-2.5 -2 -0.059933527\\ +-2 -2 0.30807174\\ +-2 -1.5 0.59847214\\ +-2.5 -1.5 0.22419478\\ +-2 -2 0.30807174\\ +-1.5 -2 0.59847214\\ +-1.5 -1.5 0.85225051\\ +-2 -1.5 0.59847214\\ +-1.5 -2 0.59847214\\ +-1 -2 0.78674913\\ +-1 -1.5 0.97321325\\ +-1.5 -1.5 0.85225051\\ +-1 -2 0.78674913\\ +-0.5 -2 0.88197658\\ +-0.5 -1.5 0.99994652\\ +-1 -1.5 0.97321325\\ +-0.5 -2 0.88197658\\ +0 -2 0.90929743\\ +0 -1.5 0.99749499\\ +-0.5 -1.5 0.99994652\\ +0 -2 0.90929743\\ +0.5 -2 0.88197658\\ +0.5 -1.5 0.99994652\\ +0 -1.5 0.99749499\\ +0.5 -2 0.88197658\\ +1 -2 0.78674913\\ +1 -1.5 0.97321325\\ +0.5 -1.5 0.99994652\\ +1 -2 0.78674913\\ +1.5 -2 0.59847214\\ +1.5 -1.5 0.85225051\\ +1 -1.5 0.97321325\\ +1.5 -2 0.59847214\\ +2 -2 0.30807174\\ +2 -1.5 0.59847214\\ +1.5 -1.5 0.85225051\\ +2 -2 0.30807174\\ +2.5 -2 -0.059933527\\ +2.5 -1.5 0.22419478\\ +2 -1.5 0.59847214\\ +2.5 -2 -0.059933527\\ +3 -2 -0.44749175\\ +3 -1.5 -0.21091343\\ +2.5 -1.5 0.22419478\\ +3 -2 -0.44749175\\ +3.5 -2 -0.77677976\\ +3.5 -1.5 -0.61807681\\ +3 -1.5 -0.21091343\\ +3.5 -2 -0.77677976\\ +4 -2 -0.9712778\\ +4 -1.5 -0.90458671\\ +3.5 -1.5 -0.61807681\\ +4 -2 -0.9712778\\ +4.5 -2 -0.97760364\\ +4.5 -1.5 -0.99951869\\ +4 -1.5 -0.90458671\\ +-5 -1.5 -0.87383376\\ +-4.5 -1.5 -0.99951869\\ +-4.5 -1 -0.99473952\\ +-5 -1 -0.92618484\\ +-4.5 -1.5 -0.99951869\\ +-4 -1.5 -0.90458671\\ +-4 -1 -0.83133918\\ +-4.5 -1 -0.99473952\\ +-4 -1.5 -0.90458671\\ +-3.5 -1.5 -0.61807681\\ +-3.5 -1 -0.47807551\\ +-4 -1 -0.83133918\\ +-3.5 -1.5 -0.61807681\\ +-3 -1.5 -0.21091343\\ +-3 -1 -0.020683532\\ +-3.5 -1 -0.47807551\\ +-3 -1.5 -0.21091343\\ +-2.5 -1.5 0.22419478\\ +-2.5 -1 0.4340741\\ +-3 -1 -0.020683532\\ +-2.5 -1.5 0.22419478\\ +-2 -1.5 0.59847214\\ +-2 -1 0.78674913\\ +-2.5 -1 0.4340741\\ +-2 -1.5 0.59847214\\ +-1.5 -1.5 0.85225051\\ +-1.5 -1 0.97321325\\ +-2 -1 0.78674913\\ +-1.5 -1.5 0.85225051\\ +-1 -1.5 0.97321325\\ +-1 -1 0.98776595\\ +-1.5 -1 0.97321325\\ +-1 -1.5 0.97321325\\ +-0.5 -1.5 0.99994652\\ +-0.5 -1 0.89924215\\ +-1 -1 0.98776595\\ +-0.5 -1.5 0.99994652\\ +0 -1.5 0.99749499\\ +0 -1 0.84147098\\ +-0.5 -1 0.89924215\\ +0 -1.5 0.99749499\\ +0.5 -1.5 0.99994652\\ +0.5 -1 0.89924215\\ +0 -1 0.84147098\\ +0.5 -1.5 0.99994652\\ +1 -1.5 0.97321325\\ +1 -1 0.98776595\\ +0.5 -1 0.89924215\\ +1 -1.5 0.97321325\\ +1.5 -1.5 0.85225051\\ +1.5 -1 0.97321325\\ +1 -1 0.98776595\\ +1.5 -1.5 0.85225051\\ +2 -1.5 0.59847214\\ +2 -1 0.78674913\\ +1.5 -1 0.97321325\\ +2 -1.5 0.59847214\\ +2.5 -1.5 0.22419478\\ +2.5 -1 0.4340741\\ +2 -1 0.78674913\\ +2.5 -1.5 0.22419478\\ +3 -1.5 -0.21091343\\ +3 -1 -0.020683532\\ +2.5 -1 0.4340741\\ +3 -1.5 -0.21091343\\ +3.5 -1.5 -0.61807681\\ +3.5 -1 -0.47807551\\ +3 -1 -0.020683532\\ +3.5 -1.5 -0.61807681\\ +4 -1.5 -0.90458671\\ +4 -1 -0.83133918\\ +3.5 -1 -0.47807551\\ +4 -1.5 -0.90458671\\ +4.5 -1.5 -0.99951869\\ +4.5 -1 -0.99473952\\ +4 -1 -0.83133918\\ +-5 -1 -0.92618484\\ +-4.5 -1 -0.99473952\\ +-4.5 -0.5 -0.98299205\\ +-5 -0.5 -0.95155293\\ +-4.5 -1 -0.99473952\\ +-4 -1 -0.83133918\\ +-4 -0.5 -0.77677976\\ +-4.5 -0.5 -0.98299205\\ +-4 -1 -0.83133918\\ +-3.5 -1 -0.47807551\\ +-3.5 -0.5 -0.38383075\\ +-4 -0.5 -0.77677976\\ +-3.5 -1 -0.47807551\\ +-3 -1 -0.020683532\\ +-3 -0.5 0.10004375\\ +-3.5 -0.5 -0.38383075\\ +-3 -1 -0.020683532\\ +-2.5 -1 0.4340741\\ +-2.5 -0.5 0.55809058\\ +-3 -0.5 0.10004375\\ +-2.5 -1 0.4340741\\ +-2 -1 0.78674913\\ +-2 -0.5 0.88197658\\ +-2.5 -0.5 0.55809058\\ +-2 -1 0.78674913\\ +-1.5 -1 0.97321325\\ +-1.5 -0.5 0.99994652\\ +-2 -0.5 0.88197658\\ +-1.5 -1 0.97321325\\ +-1 -1 0.98776595\\ +-1 -0.5 0.89924215\\ +-1.5 -0.5 0.99994652\\ +-1 -1 0.98776595\\ +-0.5 -1 0.89924215\\ +-0.5 -0.5 0.64963694\\ +-1 -0.5 0.89924215\\ +-0.5 -1 0.89924215\\ +0 -1 0.84147098\\ +0 -0.5 0.47942554\\ +-0.5 -0.5 0.64963694\\ +0 -1 0.84147098\\ +0.5 -1 0.89924215\\ +0.5 -0.5 0.64963694\\ +0 -0.5 0.47942554\\ +0.5 -1 0.89924215\\ +1 -1 0.98776595\\ +1 -0.5 0.89924215\\ +0.5 -0.5 0.64963694\\ +1 -1 0.98776595\\ +1.5 -1 0.97321325\\ +1.5 -0.5 0.99994652\\ +1 -0.5 0.89924215\\ +1.5 -1 0.97321325\\ +2 -1 0.78674913\\ +2 -0.5 0.88197658\\ +1.5 -0.5 0.99994652\\ +2 -1 0.78674913\\ +2.5 -1 0.4340741\\ +2.5 -0.5 0.55809058\\ +2 -0.5 0.88197658\\ +2.5 -1 0.4340741\\ +3 -1 -0.020683532\\ +3 -0.5 0.10004375\\ +2.5 -0.5 0.55809058\\ +3 -1 -0.020683532\\ +3.5 -1 -0.47807551\\ +3.5 -0.5 -0.38383075\\ +3 -0.5 0.10004375\\ +3.5 -1 -0.47807551\\ +4 -1 -0.83133918\\ +4 -0.5 -0.77677976\\ +3.5 -0.5 -0.38383075\\ +4 -1 -0.83133918\\ +4.5 -1 -0.99473952\\ +4.5 -0.5 -0.98299205\\ +4 -0.5 -0.77677976\\ +-5 -0.5 -0.95155293\\ +-4.5 -0.5 -0.98299205\\ +-4.5 0 -0.97753012\\ +-5 0 -0.95892427\\ +-4.5 -0.5 -0.98299205\\ +-4 -0.5 -0.77677976\\ +-4 0 -0.7568025\\ +-4.5 0 -0.97753012\\ +-4 -0.5 -0.77677976\\ +-3.5 -0.5 -0.38383075\\ +-3.5 0 -0.35078323\\ +-4 0 -0.7568025\\ +-3.5 -0.5 -0.38383075\\ +-3 -0.5 0.10004375\\ +-3 0 0.14112001\\ +-3.5 0 -0.35078323\\ +-3 -0.5 0.10004375\\ +-2.5 -0.5 0.55809058\\ +-2.5 0 0.59847214\\ +-3 0 0.14112001\\ +-2.5 -0.5 0.55809058\\ +-2 -0.5 0.88197658\\ +-2 0 0.90929743\\ +-2.5 0 0.59847214\\ +-2 -0.5 0.88197658\\ +-1.5 -0.5 0.99994652\\ +-1.5 0 0.99749499\\ +-2 0 0.90929743\\ +-1.5 -0.5 0.99994652\\ +-1 -0.5 0.89924215\\ +-1 0 0.84147098\\ +-1.5 0 0.99749499\\ +-1 -0.5 0.89924215\\ +-0.5 -0.5 0.64963694\\ +-0.5 0 0.47942554\\ +-1 0 0.84147098\\ +-0.5 -0.5 0.64963694\\ +0 -0.5 0.47942554\\ +0 0 0\\ +-0.5 0 0.47942554\\ +0 -0.5 0.47942554\\ +0.5 -0.5 0.64963694\\ +0.5 0 0.47942554\\ +0 0 0\\ +0.5 -0.5 0.64963694\\ +1 -0.5 0.89924215\\ +1 0 0.84147098\\ +0.5 0 0.47942554\\ +1 -0.5 0.89924215\\ +1.5 -0.5 0.99994652\\ +1.5 0 0.99749499\\ +1 0 0.84147098\\ +1.5 -0.5 0.99994652\\ +2 -0.5 0.88197658\\ +2 0 0.90929743\\ +1.5 0 0.99749499\\ +2 -0.5 0.88197658\\ +2.5 -0.5 0.55809058\\ +2.5 0 0.59847214\\ +2 0 0.90929743\\ +2.5 -0.5 0.55809058\\ +3 -0.5 0.10004375\\ +3 0 0.14112001\\ +2.5 0 0.59847214\\ +3 -0.5 0.10004375\\ +3.5 -0.5 -0.38383075\\ +3.5 0 -0.35078323\\ +3 0 0.14112001\\ +3.5 -0.5 -0.38383075\\ +4 -0.5 -0.77677976\\ +4 0 -0.7568025\\ +3.5 0 -0.35078323\\ +4 -0.5 -0.77677976\\ +4.5 -0.5 -0.98299205\\ +4.5 0 -0.97753012\\ +4 0 -0.7568025\\ +-5 0 -0.95892427\\ +-4.5 0 -0.97753012\\ +-4.5 0.5 -0.98299205\\ +-5 0.5 -0.95155293\\ +-4.5 0 -0.97753012\\ +-4 0 -0.7568025\\ +-4 0.5 -0.77677976\\ +-4.5 0.5 -0.98299205\\ +-4 0 -0.7568025\\ +-3.5 0 -0.35078323\\ +-3.5 0.5 -0.38383075\\ +-4 0.5 -0.77677976\\ +-3.5 0 -0.35078323\\ +-3 0 0.14112001\\ +-3 0.5 0.10004375\\ +-3.5 0.5 -0.38383075\\ +-3 0 0.14112001\\ +-2.5 0 0.59847214\\ +-2.5 0.5 0.55809058\\ +-3 0.5 0.10004375\\ +-2.5 0 0.59847214\\ +-2 0 0.90929743\\ +-2 0.5 0.88197658\\ +-2.5 0.5 0.55809058\\ +-2 0 0.90929743\\ +-1.5 0 0.99749499\\ +-1.5 0.5 0.99994652\\ +-2 0.5 0.88197658\\ +-1.5 0 0.99749499\\ +-1 0 0.84147098\\ +-1 0.5 0.89924215\\ +-1.5 0.5 0.99994652\\ +-1 0 0.84147098\\ +-0.5 0 0.47942554\\ +-0.5 0.5 0.64963694\\ +-1 0.5 0.89924215\\ +-0.5 0 0.47942554\\ +0 0 0\\ +0 0.5 0.47942554\\ +-0.5 0.5 0.64963694\\ +0 0 0\\ +0.5 0 0.47942554\\ +0.5 0.5 0.64963694\\ +0 0.5 0.47942554\\ +0.5 0 0.47942554\\ +1 0 0.84147098\\ +1 0.5 0.89924215\\ +0.5 0.5 0.64963694\\ +1 0 0.84147098\\ +1.5 0 0.99749499\\ +1.5 0.5 0.99994652\\ +1 0.5 0.89924215\\ +1.5 0 0.99749499\\ +2 0 0.90929743\\ +2 0.5 0.88197658\\ +1.5 0.5 0.99994652\\ +2 0 0.90929743\\ +2.5 0 0.59847214\\ +2.5 0.5 0.55809058\\ +2 0.5 0.88197658\\ +2.5 0 0.59847214\\ +3 0 0.14112001\\ +3 0.5 0.10004375\\ +2.5 0.5 0.55809058\\ +3 0 0.14112001\\ +3.5 0 -0.35078323\\ +3.5 0.5 -0.38383075\\ +3 0.5 0.10004375\\ +3.5 0 -0.35078323\\ +4 0 -0.7568025\\ +4 0.5 -0.77677976\\ +3.5 0.5 -0.38383075\\ +4 0 -0.7568025\\ +4.5 0 -0.97753012\\ +4.5 0.5 -0.98299205\\ +4 0.5 -0.77677976\\ +-5 0.5 -0.95155293\\ +-4.5 0.5 -0.98299205\\ +-4.5 1 -0.99473952\\ +-5 1 -0.92618484\\ +-4.5 0.5 -0.98299205\\ +-4 0.5 -0.77677976\\ +-4 1 -0.83133918\\ +-4.5 1 -0.99473952\\ +-4 0.5 -0.77677976\\ +-3.5 0.5 -0.38383075\\ +-3.5 1 -0.47807551\\ +-4 1 -0.83133918\\ +-3.5 0.5 -0.38383075\\ +-3 0.5 0.10004375\\ +-3 1 -0.020683532\\ +-3.5 1 -0.47807551\\ +-3 0.5 0.10004375\\ +-2.5 0.5 0.55809058\\ +-2.5 1 0.4340741\\ +-3 1 -0.020683532\\ +-2.5 0.5 0.55809058\\ +-2 0.5 0.88197658\\ +-2 1 0.78674913\\ +-2.5 1 0.4340741\\ +-2 0.5 0.88197658\\ +-1.5 0.5 0.99994652\\ +-1.5 1 0.97321325\\ +-2 1 0.78674913\\ +-1.5 0.5 0.99994652\\ +-1 0.5 0.89924215\\ +-1 1 0.98776595\\ +-1.5 1 0.97321325\\ +-1 0.5 0.89924215\\ +-0.5 0.5 0.64963694\\ +-0.5 1 0.89924215\\ +-1 1 0.98776595\\ +-0.5 0.5 0.64963694\\ +0 0.5 0.47942554\\ +0 1 0.84147098\\ +-0.5 1 0.89924215\\ +0 0.5 0.47942554\\ +0.5 0.5 0.64963694\\ +0.5 1 0.89924215\\ +0 1 0.84147098\\ +0.5 0.5 0.64963694\\ +1 0.5 0.89924215\\ +1 1 0.98776595\\ +0.5 1 0.89924215\\ +1 0.5 0.89924215\\ +1.5 0.5 0.99994652\\ +1.5 1 0.97321325\\ +1 1 0.98776595\\ +1.5 0.5 0.99994652\\ +2 0.5 0.88197658\\ +2 1 0.78674913\\ +1.5 1 0.97321325\\ +2 0.5 0.88197658\\ +2.5 0.5 0.55809058\\ +2.5 1 0.4340741\\ +2 1 0.78674913\\ +2.5 0.5 0.55809058\\ +3 0.5 0.10004375\\ +3 1 -0.020683532\\ +2.5 1 0.4340741\\ +3 0.5 0.10004375\\ +3.5 0.5 -0.38383075\\ +3.5 1 -0.47807551\\ +3 1 -0.020683532\\ +3.5 0.5 -0.38383075\\ +4 0.5 -0.77677976\\ +4 1 -0.83133918\\ +3.5 1 -0.47807551\\ +4 0.5 -0.77677976\\ +4.5 0.5 -0.98299205\\ +4.5 1 -0.99473952\\ +4 1 -0.83133918\\ +-5 1 -0.92618484\\ +-4.5 1 -0.99473952\\ +-4.5 1.5 -0.99951869\\ +-5 1.5 -0.87383376\\ +-4.5 1 -0.99473952\\ +-4 1 -0.83133918\\ +-4 1.5 -0.90458671\\ +-4.5 1.5 -0.99951869\\ +-4 1 -0.83133918\\ +-3.5 1 -0.47807551\\ +-3.5 1.5 -0.61807681\\ +-4 1.5 -0.90458671\\ +-3.5 1 -0.47807551\\ +-3 1 -0.020683532\\ +-3 1.5 -0.21091343\\ +-3.5 1.5 -0.61807681\\ +-3 1 -0.020683532\\ +-2.5 1 0.4340741\\ +-2.5 1.5 0.22419478\\ +-3 1.5 -0.21091343\\ +-2.5 1 0.4340741\\ +-2 1 0.78674913\\ +-2 1.5 0.59847214\\ +-2.5 1.5 0.22419478\\ +-2 1 0.78674913\\ +-1.5 1 0.97321325\\ +-1.5 1.5 0.85225051\\ +-2 1.5 0.59847214\\ +-1.5 1 0.97321325\\ +-1 1 0.98776595\\ +-1 1.5 0.97321325\\ +-1.5 1.5 0.85225051\\ +-1 1 0.98776595\\ +-0.5 1 0.89924215\\ +-0.5 1.5 0.99994652\\ +-1 1.5 0.97321325\\ +-0.5 1 0.89924215\\ +0 1 0.84147098\\ +0 1.5 0.99749499\\ +-0.5 1.5 0.99994652\\ +0 1 0.84147098\\ +0.5 1 0.89924215\\ +0.5 1.5 0.99994652\\ +0 1.5 0.99749499\\ +0.5 1 0.89924215\\ +1 1 0.98776595\\ +1 1.5 0.97321325\\ +0.5 1.5 0.99994652\\ +1 1 0.98776595\\ +1.5 1 0.97321325\\ +1.5 1.5 0.85225051\\ +1 1.5 0.97321325\\ +1.5 1 0.97321325\\ +2 1 0.78674913\\ +2 1.5 0.59847214\\ +1.5 1.5 0.85225051\\ +2 1 0.78674913\\ +2.5 1 0.4340741\\ +2.5 1.5 0.22419478\\ +2 1.5 0.59847214\\ +2.5 1 0.4340741\\ +3 1 -0.020683532\\ +3 1.5 -0.21091343\\ +2.5 1.5 0.22419478\\ +3 1 -0.020683532\\ +3.5 1 -0.47807551\\ +3.5 1.5 -0.61807681\\ +3 1.5 -0.21091343\\ +3.5 1 -0.47807551\\ +4 1 -0.83133918\\ +4 1.5 -0.90458671\\ +3.5 1.5 -0.61807681\\ +4 1 -0.83133918\\ +4.5 1 -0.99473952\\ +4.5 1.5 -0.99951869\\ +4 1.5 -0.90458671\\ +-5 1.5 -0.87383376\\ +-4.5 1.5 -0.99951869\\ +-4.5 2 -0.97760364\\ +-5 2 -0.7820949\\ +-4.5 1.5 -0.99951869\\ +-4 1.5 -0.90458671\\ +-4 2 -0.9712778\\ +-4.5 2 -0.97760364\\ +-4 1.5 -0.90458671\\ +-3.5 1.5 -0.61807681\\ +-3.5 2 -0.77677976\\ +-4 2 -0.9712778\\ +-3.5 1.5 -0.61807681\\ +-3 1.5 -0.21091343\\ +-3 2 -0.44749175\\ +-3.5 2 -0.77677976\\ +-3 1.5 -0.21091343\\ +-2.5 1.5 0.22419478\\ +-2.5 2 -0.059933527\\ +-3 2 -0.44749175\\ +-2.5 1.5 0.22419478\\ +-2 1.5 0.59847214\\ +-2 2 0.30807174\\ +-2.5 2 -0.059933527\\ +-2 1.5 0.59847214\\ +-1.5 1.5 0.85225051\\ +-1.5 2 0.59847214\\ +-2 2 0.30807174\\ +-1.5 1.5 0.85225051\\ +-1 1.5 0.97321325\\ +-1 2 0.78674913\\ +-1.5 2 0.59847214\\ +-1 1.5 0.97321325\\ +-0.5 1.5 0.99994652\\ +-0.5 2 0.88197658\\ +-1 2 0.78674913\\ +-0.5 1.5 0.99994652\\ +0 1.5 0.99749499\\ +0 2 0.90929743\\ +-0.5 2 0.88197658\\ +0 1.5 0.99749499\\ +0.5 1.5 0.99994652\\ +0.5 2 0.88197658\\ +0 2 0.90929743\\ +0.5 1.5 0.99994652\\ +1 1.5 0.97321325\\ +1 2 0.78674913\\ +0.5 2 0.88197658\\ +1 1.5 0.97321325\\ +1.5 1.5 0.85225051\\ +1.5 2 0.59847214\\ +1 2 0.78674913\\ +1.5 1.5 0.85225051\\ +2 1.5 0.59847214\\ +2 2 0.30807174\\ +1.5 2 0.59847214\\ +2 1.5 0.59847214\\ +2.5 1.5 0.22419478\\ +2.5 2 -0.059933527\\ +2 2 0.30807174\\ +2.5 1.5 0.22419478\\ +3 1.5 -0.21091343\\ +3 2 -0.44749175\\ +2.5 2 -0.059933527\\ +3 1.5 -0.21091343\\ +3.5 1.5 -0.61807681\\ +3.5 2 -0.77677976\\ +3 2 -0.44749175\\ +3.5 1.5 -0.61807681\\ +4 1.5 -0.90458671\\ +4 2 -0.9712778\\ +3.5 2 -0.77677976\\ +4 1.5 -0.90458671\\ +4.5 1.5 -0.99951869\\ +4.5 2 -0.97760364\\ +4 2 -0.9712778\\ +-5 2 -0.7820949\\ +-4.5 2 -0.97760364\\ +-4.5 2.5 -0.9066904\\ +-5 2.5 -0.63885987\\ +-4.5 2 -0.97760364\\ +-4 2 -0.9712778\\ +-4 2.5 -0.99998941\\ +-4.5 2.5 -0.9066904\\ +-4 2 -0.9712778\\ +-3.5 2 -0.77677976\\ +-3.5 2.5 -0.9166313\\ +-4 2.5 -0.99998941\\ +-3.5 2 -0.77677976\\ +-3 2 -0.44749175\\ +-3 2.5 -0.6914774\\ +-3.5 2.5 -0.9166313\\ +-3 2 -0.44749175\\ +-2.5 2 -0.059933527\\ +-2.5 2.5 -0.38383075\\ +-3 2.5 -0.6914774\\ +-2.5 2 -0.059933527\\ +-2 2 0.30807174\\ +-2 2.5 -0.059933527\\ +-2.5 2.5 -0.38383075\\ +-2 2 0.30807174\\ +-1.5 2 0.59847214\\ +-1.5 2.5 0.22419478\\ +-2 2.5 -0.059933527\\ +-1.5 2 0.59847214\\ +-1 2 0.78674913\\ +-1 2.5 0.4340741\\ +-1.5 2.5 0.22419478\\ +-1 2 0.78674913\\ +-0.5 2 0.88197658\\ +-0.5 2.5 0.55809058\\ +-1 2.5 0.4340741\\ +-0.5 2 0.88197658\\ +0 2 0.90929743\\ +0 2.5 0.59847214\\ +-0.5 2.5 0.55809058\\ +0 2 0.90929743\\ +0.5 2 0.88197658\\ +0.5 2.5 0.55809058\\ +0 2.5 0.59847214\\ +0.5 2 0.88197658\\ +1 2 0.78674913\\ +1 2.5 0.4340741\\ +0.5 2.5 0.55809058\\ +1 2 0.78674913\\ +1.5 2 0.59847214\\ +1.5 2.5 0.22419478\\ +1 2.5 0.4340741\\ +1.5 2 0.59847214\\ +2 2 0.30807174\\ +2 2.5 -0.059933527\\ +1.5 2.5 0.22419478\\ +2 2 0.30807174\\ +2.5 2 -0.059933527\\ +2.5 2.5 -0.38383075\\ +2 2.5 -0.059933527\\ +2.5 2 -0.059933527\\ +3 2 -0.44749175\\ +3 2.5 -0.6914774\\ +2.5 2.5 -0.38383075\\ +3 2 -0.44749175\\ +3.5 2 -0.77677976\\ +3.5 2.5 -0.9166313\\ +3 2.5 -0.6914774\\ +3.5 2 -0.77677976\\ +4 2 -0.9712778\\ +4 2.5 -0.99998941\\ +3.5 2.5 -0.9166313\\ +4 2 -0.9712778\\ +4.5 2 -0.97760364\\ +4.5 2.5 -0.9066904\\ +4 2.5 -0.99998941\\ +-5 2.5 -0.63885987\\ +-4.5 2.5 -0.9066904\\ +-4.5 3 -0.76745273\\ +-5 3 -0.43697552\\ +-4.5 2.5 -0.9066904\\ +-4 2.5 -0.99998941\\ +-4 3 -0.95892427\\ +-4.5 3 -0.76745273\\ +-4 2.5 -0.99998941\\ +-3.5 2.5 -0.9166313\\ +-3.5 3 -0.99473952\\ +-4 3 -0.95892427\\ +-3.5 2.5 -0.9166313\\ +-3 2.5 -0.6914774\\ +-3 3 -0.89168225\\ +-3.5 3 -0.99473952\\ +-3 2.5 -0.6914774\\ +-2.5 2.5 -0.38383075\\ +-2.5 3 -0.6914774\\ +-3 3 -0.89168225\\ +-2.5 2.5 -0.38383075\\ +-2 2.5 -0.059933527\\ +-2 3 -0.44749175\\ +-2.5 3 -0.6914774\\ +-2 2.5 -0.059933527\\ +-1.5 2.5 0.22419478\\ +-1.5 3 -0.21091343\\ +-2 3 -0.44749175\\ +-1.5 2.5 0.22419478\\ +-1 2.5 0.4340741\\ +-1 3 -0.020683532\\ +-1.5 3 -0.21091343\\ +-1 2.5 0.4340741\\ +-0.5 2.5 0.55809058\\ +-0.5 3 0.10004375\\ +-1 3 -0.020683532\\ +-0.5 2.5 0.55809058\\ +0 2.5 0.59847214\\ +0 3 0.14112001\\ +-0.5 3 0.10004375\\ +0 2.5 0.59847214\\ +0.5 2.5 0.55809058\\ +0.5 3 0.10004375\\ +0 3 0.14112001\\ +0.5 2.5 0.55809058\\ +1 2.5 0.4340741\\ +1 3 -0.020683532\\ +0.5 3 0.10004375\\ +1 2.5 0.4340741\\ +1.5 2.5 0.22419478\\ +1.5 3 -0.21091343\\ +1 3 -0.020683532\\ +1.5 2.5 0.22419478\\ +2 2.5 -0.059933527\\ +2 3 -0.44749175\\ +1.5 3 -0.21091343\\ +2 2.5 -0.059933527\\ +2.5 2.5 -0.38383075\\ +2.5 3 -0.6914774\\ +2 3 -0.44749175\\ +2.5 2.5 -0.38383075\\ +3 2.5 -0.6914774\\ +3 3 -0.89168225\\ +2.5 3 -0.6914774\\ +3 2.5 -0.6914774\\ +3.5 2.5 -0.9166313\\ +3.5 3 -0.99473952\\ +3 3 -0.89168225\\ +3.5 2.5 -0.9166313\\ +4 2.5 -0.99998941\\ +4 3 -0.95892427\\ +3.5 3 -0.99473952\\ +4 2.5 -0.99998941\\ +4.5 2.5 -0.9066904\\ +4.5 3 -0.76745273\\ +4 3 -0.95892427\\ +-5 3 -0.43697552\\ +-4.5 3 -0.76745273\\ +-4.5 3.5 -0.54995318\\ +-5 3.5 -0.17893857\\ +-4.5 3 -0.76745273\\ +-4 3 -0.95892427\\ +-4 3.5 -0.82381719\\ +-4.5 3.5 -0.54995318\\ +-4 3 -0.95892427\\ +-3.5 3 -0.99473952\\ +-3.5 3.5 -0.97196248\\ +-4 3.5 -0.82381719\\ +-3.5 3 -0.99473952\\ +-3 3 -0.89168225\\ +-3 3.5 -0.99473952\\ +-3.5 3.5 -0.97196248\\ +-3 3 -0.89168225\\ +-2.5 3 -0.6914774\\ +-2.5 3.5 -0.9166313\\ +-3 3.5 -0.99473952\\ +-2.5 3 -0.6914774\\ +-2 3 -0.44749175\\ +-2 3.5 -0.77677976\\ +-2.5 3.5 -0.9166313\\ +-2 3 -0.44749175\\ +-1.5 3 -0.21091343\\ +-1.5 3.5 -0.61807681\\ +-2 3.5 -0.77677976\\ +-1.5 3 -0.21091343\\ +-1 3 -0.020683532\\ +-1 3.5 -0.47807551\\ +-1.5 3.5 -0.61807681\\ +-1 3 -0.020683532\\ +-0.5 3 0.10004375\\ +-0.5 3.5 -0.38383075\\ +-1 3.5 -0.47807551\\ +-0.5 3 0.10004375\\ +0 3 0.14112001\\ +0 3.5 -0.35078323\\ +-0.5 3.5 -0.38383075\\ +0 3 0.14112001\\ +0.5 3 0.10004375\\ +0.5 3.5 -0.38383075\\ +0 3.5 -0.35078323\\ +0.5 3 0.10004375\\ +1 3 -0.020683532\\ +1 3.5 -0.47807551\\ +0.5 3.5 -0.38383075\\ +1 3 -0.020683532\\ +1.5 3 -0.21091343\\ +1.5 3.5 -0.61807681\\ +1 3.5 -0.47807551\\ +1.5 3 -0.21091343\\ +2 3 -0.44749175\\ +2 3.5 -0.77677976\\ +1.5 3.5 -0.61807681\\ +2 3 -0.44749175\\ +2.5 3 -0.6914774\\ +2.5 3.5 -0.9166313\\ +2 3.5 -0.77677976\\ +2.5 3 -0.6914774\\ +3 3 -0.89168225\\ +3 3.5 -0.99473952\\ +2.5 3.5 -0.9166313\\ +3 3 -0.89168225\\ +3.5 3 -0.99473952\\ +3.5 3.5 -0.97196248\\ +3 3.5 -0.99473952\\ +3.5 3 -0.99473952\\ +4 3 -0.95892427\\ +4 3.5 -0.82381719\\ +3.5 3.5 -0.97196248\\ +4 3 -0.95892427\\ +4.5 3 -0.76745273\\ +4.5 3.5 -0.54995318\\ +4 3.5 -0.82381719\\ +-5 3.5 -0.17893857\\ +-4.5 3.5 -0.54995318\\ +-4.5 4 -0.25938757\\ +-5 4 0.11965158\\ +-4.5 3.5 -0.54995318\\ +-4 3.5 -0.82381719\\ +-4 4 -0.58617619\\ +-4.5 4 -0.25938757\\ +-4 3.5 -0.82381719\\ +-3.5 3.5 -0.97196248\\ +-3.5 4 -0.82381719\\ +-4 4 -0.58617619\\ +-3.5 3.5 -0.97196248\\ +-3 3.5 -0.99473952\\ +-3 4 -0.95892427\\ +-3.5 4 -0.82381719\\ +-3 3.5 -0.99473952\\ +-2.5 3.5 -0.9166313\\ +-2.5 4 -0.99998941\\ +-3 4 -0.95892427\\ +-2.5 3.5 -0.9166313\\ +-2 3.5 -0.77677976\\ +-2 4 -0.9712778\\ +-2.5 4 -0.99998941\\ +-2 3.5 -0.77677976\\ +-1.5 3.5 -0.61807681\\ +-1.5 4 -0.90458671\\ +-2 4 -0.9712778\\ +-1.5 3.5 -0.61807681\\ +-1 3.5 -0.47807551\\ +-1 4 -0.83133918\\ +-1.5 4 -0.90458671\\ +-1 3.5 -0.47807551\\ +-0.5 3.5 -0.38383075\\ +-0.5 4 -0.77677976\\ +-1 4 -0.83133918\\ +-0.5 3.5 -0.38383075\\ +0 3.5 -0.35078323\\ +0 4 -0.7568025\\ +-0.5 4 -0.77677976\\ +0 3.5 -0.35078323\\ +0.5 3.5 -0.38383075\\ +0.5 4 -0.77677976\\ +0 4 -0.7568025\\ +0.5 3.5 -0.38383075\\ +1 3.5 -0.47807551\\ +1 4 -0.83133918\\ +0.5 4 -0.77677976\\ +1 3.5 -0.47807551\\ +1.5 3.5 -0.61807681\\ +1.5 4 -0.90458671\\ +1 4 -0.83133918\\ +1.5 3.5 -0.61807681\\ +2 3.5 -0.77677976\\ +2 4 -0.9712778\\ +1.5 4 -0.90458671\\ +2 3.5 -0.77677976\\ +2.5 3.5 -0.9166313\\ +2.5 4 -0.99998941\\ +2 4 -0.9712778\\ +2.5 3.5 -0.9166313\\ +3 3.5 -0.99473952\\ +3 4 -0.95892427\\ +2.5 4 -0.99998941\\ +3 3.5 -0.99473952\\ +3.5 3.5 -0.97196248\\ +3.5 4 -0.82381719\\ +3 4 -0.95892427\\ +3.5 3.5 -0.97196248\\ +4 3.5 -0.82381719\\ +4 4 -0.58617619\\ +3.5 4 -0.82381719\\ +4 3.5 -0.82381719\\ +4.5 3.5 -0.54995318\\ +4.5 4 -0.25938757\\ +4 4 -0.58617619\\ +-5 4 0.11965158\\ +-4.5 4 -0.25938757\\ +-4.5 4.5 0.080687912\\ +-5 4.5 0.42921793\\ +-4.5 4 -0.25938757\\ +-4 4 -0.58617619\\ +-4 4.5 -0.25938757\\ +-4.5 4.5 0.080687912\\ +-4 4 -0.58617619\\ +-3.5 4 -0.82381719\\ +-3.5 4.5 -0.54995318\\ +-4 4.5 -0.25938757\\ +-3.5 4 -0.82381719\\ +-3 4 -0.95892427\\ +-3 4.5 -0.76745273\\ +-3.5 4.5 -0.54995318\\ +-3 4 -0.95892427\\ +-2.5 4 -0.99998941\\ +-2.5 4.5 -0.9066904\\ +-3 4.5 -0.76745273\\ +-2.5 4 -0.99998941\\ +-2 4 -0.9712778\\ +-2 4.5 -0.97760364\\ +-2.5 4.5 -0.9066904\\ +-2 4 -0.9712778\\ +-1.5 4 -0.90458671\\ +-1.5 4.5 -0.99951869\\ +-2 4.5 -0.97760364\\ +-1.5 4 -0.90458671\\ +-1 4 -0.83133918\\ +-1 4.5 -0.99473952\\ +-1.5 4.5 -0.99951869\\ +-1 4 -0.83133918\\ +-0.5 4 -0.77677976\\ +-0.5 4.5 -0.98299205\\ +-1 4.5 -0.99473952\\ +-0.5 4 -0.77677976\\ +0 4 -0.7568025\\ +0 4.5 -0.97753012\\ +-0.5 4.5 -0.98299205\\ +0 4 -0.7568025\\ +0.5 4 -0.77677976\\ +0.5 4.5 -0.98299205\\ +0 4.5 -0.97753012\\ +0.5 4 -0.77677976\\ +1 4 -0.83133918\\ +1 4.5 -0.99473952\\ +0.5 4.5 -0.98299205\\ +1 4 -0.83133918\\ +1.5 4 -0.90458671\\ +1.5 4.5 -0.99951869\\ +1 4.5 -0.99473952\\ +1.5 4 -0.90458671\\ +2 4 -0.9712778\\ +2 4.5 -0.97760364\\ +1.5 4.5 -0.99951869\\ +2 4 -0.9712778\\ +2.5 4 -0.99998941\\ +2.5 4.5 -0.9066904\\ +2 4.5 -0.97760364\\ +2.5 4 -0.99998941\\ +3 4 -0.95892427\\ +3 4.5 -0.76745273\\ +2.5 4.5 -0.9066904\\ +3 4 -0.95892427\\ +3.5 4 -0.82381719\\ +3.5 4.5 -0.54995318\\ +3 4.5 -0.76745273\\ +3.5 4 -0.82381719\\ +4 4 -0.58617619\\ +4 4.5 -0.25938757\\ +3.5 4.5 -0.54995318\\ +4 4 -0.58617619\\ +4.5 4 -0.25938757\\ +4.5 4.5 0.080687912\\ +4 4.5 -0.25938757\\ +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +-5 -5 1.4588613 +-4.5 -5 1.1792179 +-4 -5 0.86965158 +-3.5 -5 0.57106143 +-3 -5 0.31302448 +-2.5 -5 0.11114013 +-2 -5 -0.032094899 +-1.5 -5 -0.12383376 +-1 -5 -0.17618484 +-0.5 -5 -0.20155293 +0 -5 -0.20892427 +0.5 -5 -0.20155293 +1 -5 -0.17618484 +1.5 -5 -0.12383376 +2 -5 -0.032094899 +2.5 -5 0.11114013 +3 -5 0.31302448 +3.5 -5 0.57106143 +4 -5 0.86965158 +4.5 -5 1.1792179 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +-5 -4 0.86965158 +-4.5 -4 0.49061243 +-4 -4 0.16382381 +-3.5 -4 -0.073817185 +-3 -4 -0.20892427 +-2.5 -4 -0.24998941 +-2 -4 -0.2212778 +-1.5 -4 -0.15458671 +-1 -4 -0.081339179 +-0.5 -4 -0.026779756 +0 -4 -0.0068024953 +0.5 -4 -0.026779756 +1 -4 -0.081339179 +1.5 -4 -0.15458671 +2 -4 -0.2212778 +2.5 -4 -0.24998941 +3 -4 -0.20892427 +3.5 -4 -0.073817185 +4 -4 0.16382381 +4.5 -4 0.49061243 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +-5 -3 0.31302448 +-4.5 -3 -0.017452726 +-4 -3 -0.20892427 +-3.5 -3 -0.24473952 +-3 -3 -0.14168225 +-2.5 -3 0.058522603 +-2 -3 0.30250825 +-1.5 -3 0.53908657 +-1 -3 0.72931647 +-0.5 -3 0.85004375 +0 -3 0.89112001 +0.5 -3 0.85004375 +1 -3 0.72931647 +1.5 -3 0.53908657 +2 -3 0.30250825 +2.5 -3 0.058522603 +3 -3 -0.14168225 +3.5 -3 -0.24473952 +4 -3 -0.20892427 +4.5 -3 -0.017452726 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +-5 -2 -0.032094899 +-4.5 -2 -0.22760364 +-4 -2 -0.2212778 +-3.5 -2 -0.026779756 +-3 -2 0.30250825 +-2.5 -2 0.69006647 +-2 -2 1.0580717 +-1.5 -2 1.3484721 +-1 -2 1.5367491 +-0.5 -2 1.6319766 +0 -2 1.6592974 +0.5 -2 1.6319766 +1 -2 1.5367491 +1.5 -2 1.3484721 +2 -2 1.0580717 +2.5 -2 0.69006647 +3 -2 0.30250825 +3.5 -2 -0.026779756 +4 -2 -0.2212778 +4.5 -2 -0.22760364 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +-5 -1 -0.17618484 +-4.5 -1 -0.24473952 +-4 -1 -0.081339179 +-3.5 -1 0.27192449 +-3 -1 0.72931647 +-2.5 -1 1.1840741 +-2 -1 1.5367491 +-1.5 -1 1.7232132 +-1 -1 1.7377659 +-0.5 -1 1.6492421 +0 -1 1.591471 +0.5 -1 1.6492421 +1 -1 1.7377659 +1.5 -1 1.7232132 +2 -1 1.5367491 +2.5 -1 1.1840741 +3 -1 0.72931647 +3.5 -1 0.27192449 +4 -1 -0.081339179 +4.5 -1 -0.24473952 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +-5 0 -0.20892427 +-4.5 0 -0.22753012 +-4 0 -0.0068024953 +-3.5 0 0.39921677 +-3 0 0.89112001 +-2.5 0 1.3484721 +-2 0 1.6592974 +-1.5 0 1.747495 +-1 0 1.591471 +-0.5 0 1.2294255 +0 0 0.75 +0.5 0 1.2294255 +1 0 1.591471 +1.5 0 1.747495 +2 0 1.6592974 +2.5 0 1.3484721 +3 0 0.89112001 +3.5 0 0.39921677 +4 0 -0.0068024953 +4.5 0 -0.22753012 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +-5 1 -0.17618484 +-4.5 1 -0.24473952 +-4 1 -0.081339179 +-3.5 1 0.27192449 +-3 1 0.72931647 +-2.5 1 1.1840741 +-2 1 1.5367491 +-1.5 1 1.7232132 +-1 1 1.7377659 +-0.5 1 1.6492421 +0 1 1.591471 +0.5 1 1.6492421 +1 1 1.7377659 +1.5 1 1.7232132 +2 1 1.5367491 +2.5 1 1.1840741 +3 1 0.72931647 +3.5 1 0.27192449 +4 1 -0.081339179 +4.5 1 -0.24473952 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +-5 2 -0.032094899 +-4.5 2 -0.22760364 +-4 2 -0.2212778 +-3.5 2 -0.026779756 +-3 2 0.30250825 +-2.5 2 0.69006647 +-2 2 1.0580717 +-1.5 2 1.3484721 +-1 2 1.5367491 +-0.5 2 1.6319766 +0 2 1.6592974 +0.5 2 1.6319766 +1 2 1.5367491 +1.5 2 1.3484721 +2 2 1.0580717 +2.5 2 0.69006647 +3 2 0.30250825 +3.5 2 -0.026779756 +4 2 -0.2212778 +4.5 2 -0.22760364 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +-5 3 0.31302448 +-4.5 3 -0.017452726 +-4 3 -0.20892427 +-3.5 3 -0.24473952 +-3 3 -0.14168225 +-2.5 3 0.058522603 +-2 3 0.30250825 +-1.5 3 0.53908657 +-1 3 0.72931647 +-0.5 3 0.85004375 +0 3 0.89112001 +0.5 3 0.85004375 +1 3 0.72931647 +1.5 3 0.53908657 +2 3 0.30250825 +2.5 3 0.058522603 +3 3 -0.14168225 +3.5 3 -0.24473952 +4 3 -0.20892427 +4.5 3 -0.017452726 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +-5 4 0.86965158 +-4.5 4 0.49061243 +-4 4 0.16382381 +-3.5 4 -0.073817185 +-3 4 -0.20892427 +-2.5 4 -0.24998941 +-2 4 -0.2212778 +-1.5 4 -0.15458671 +-1 4 -0.081339179 +-0.5 4 -0.026779756 +0 4 -0.0068024953 +0.5 4 -0.026779756 +1 4 -0.081339179 +1.5 4 -0.15458671 +2 4 -0.2212778 +2.5 4 -0.24998941 +3 4 -0.20892427 +3.5 4 -0.073817185 +4 4 0.16382381 +4.5 4 0.49061243 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +-5 4.5 1.1792179 +-4.5 4.5 0.83068791 +-4 4.5 0.49061243 +-3.5 4.5 0.20004682 +-3 4.5 -0.017452726 +-2.5 4.5 -0.1566904 +-2 4.5 -0.22760364 +-1.5 4.5 -0.24951869 +-1 4.5 -0.24473952 +-0.5 4.5 -0.23299205 +0 4.5 -0.22753012 +0.5 4.5 -0.23299205 +1 4.5 -0.24473952 +1.5 4.5 -0.24951869 +2 4.5 -0.22760364 +2.5 4.5 -0.1566904 +3 4.5 -0.017452726 +3.5 4.5 0.20004682 +4 4.5 0.49061243 +4.5 4.5 0.83068791 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +-5 -5 1.4588613 +-5 -4.5 1.1792179 +-5 -4 0.86965158 +-5 -3.5 0.57106143 +-5 -3 0.31302448 +-5 -2.5 0.11114013 +-5 -2 -0.032094899 +-5 -1.5 -0.12383376 +-5 -1 -0.17618484 +-5 -0.5 -0.20155293 +-5 0 -0.20892427 +-5 0.5 -0.20155293 +-5 1 -0.17618484 +-5 1.5 -0.12383376 +-5 2 -0.032094899 +-5 2.5 0.11114013 +-5 3 0.31302448 +-5 3.5 0.57106143 +-5 4 0.86965158 +-5 4.5 1.1792179 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +-4 -5 0.86965158 +-4 -4.5 0.49061243 +-4 -4 0.16382381 +-4 -3.5 -0.073817185 +-4 -3 -0.20892427 +-4 -2.5 -0.24998941 +-4 -2 -0.2212778 +-4 -1.5 -0.15458671 +-4 -1 -0.081339179 +-4 -0.5 -0.026779756 +-4 0 -0.0068024953 +-4 0.5 -0.026779756 +-4 1 -0.081339179 +-4 1.5 -0.15458671 +-4 2 -0.2212778 +-4 2.5 -0.24998941 +-4 3 -0.20892427 +-4 3.5 -0.073817185 +-4 4 0.16382381 +-4 4.5 0.49061243 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +-3 -5 0.31302448 +-3 -4.5 -0.017452726 +-3 -4 -0.20892427 +-3 -3.5 -0.24473952 +-3 -3 -0.14168225 +-3 -2.5 0.058522603 +-3 -2 0.30250825 +-3 -1.5 0.53908657 +-3 -1 0.72931647 +-3 -0.5 0.85004375 +-3 0 0.89112001 +-3 0.5 0.85004375 +-3 1 0.72931647 +-3 1.5 0.53908657 +-3 2 0.30250825 +-3 2.5 0.058522603 +-3 3 -0.14168225 +-3 3.5 -0.24473952 +-3 4 -0.20892427 +-3 4.5 -0.017452726 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +-2 -5 -0.032094899 +-2 -4.5 -0.22760364 +-2 -4 -0.2212778 +-2 -3.5 -0.026779756 +-2 -3 0.30250825 +-2 -2.5 0.69006647 +-2 -2 1.0580717 +-2 -1.5 1.3484721 +-2 -1 1.5367491 +-2 -0.5 1.6319766 +-2 0 1.6592974 +-2 0.5 1.6319766 +-2 1 1.5367491 +-2 1.5 1.3484721 +-2 2 1.0580717 +-2 2.5 0.69006647 +-2 3 0.30250825 +-2 3.5 -0.026779756 +-2 4 -0.2212778 +-2 4.5 -0.22760364 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +-1 -5 -0.17618484 +-1 -4.5 -0.24473952 +-1 -4 -0.081339179 +-1 -3.5 0.27192449 +-1 -3 0.72931647 +-1 -2.5 1.1840741 +-1 -2 1.5367491 +-1 -1.5 1.7232132 +-1 -1 1.7377659 +-1 -0.5 1.6492421 +-1 0 1.591471 +-1 0.5 1.6492421 +-1 1 1.7377659 +-1 1.5 1.7232132 +-1 2 1.5367491 +-1 2.5 1.1840741 +-1 3 0.72931647 +-1 3.5 0.27192449 +-1 4 -0.081339179 +-1 4.5 -0.24473952 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +0 -5 -0.20892427 +0 -4.5 -0.22753012 +0 -4 -0.0068024953 +0 -3.5 0.39921677 +0 -3 0.89112001 +0 -2.5 1.3484721 +0 -2 1.6592974 +0 -1.5 1.747495 +0 -1 1.591471 +0 -0.5 1.2294255 +0 0 0.75 +0 0.5 1.2294255 +0 1 1.591471 +0 1.5 1.747495 +0 2 1.6592974 +0 2.5 1.3484721 +0 3 0.89112001 +0 3.5 0.39921677 +0 4 -0.0068024953 +0 4.5 -0.22753012 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +1 -5 -0.17618484 +1 -4.5 -0.24473952 +1 -4 -0.081339179 +1 -3.5 0.27192449 +1 -3 0.72931647 +1 -2.5 1.1840741 +1 -2 1.5367491 +1 -1.5 1.7232132 +1 -1 1.7377659 +1 -0.5 1.6492421 +1 0 1.591471 +1 0.5 1.6492421 +1 1 1.7377659 +1 1.5 1.7232132 +1 2 1.5367491 +1 2.5 1.1840741 +1 3 0.72931647 +1 3.5 0.27192449 +1 4 -0.081339179 +1 4.5 -0.24473952 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +2 -5 -0.032094899 +2 -4.5 -0.22760364 +2 -4 -0.2212778 +2 -3.5 -0.026779756 +2 -3 0.30250825 +2 -2.5 0.69006647 +2 -2 1.0580717 +2 -1.5 1.3484721 +2 -1 1.5367491 +2 -0.5 1.6319766 +2 0 1.6592974 +2 0.5 1.6319766 +2 1 1.5367491 +2 1.5 1.3484721 +2 2 1.0580717 +2 2.5 0.69006647 +2 3 0.30250825 +2 3.5 -0.026779756 +2 4 -0.2212778 +2 4.5 -0.22760364 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +3 -5 0.31302448 +3 -4.5 -0.017452726 +3 -4 -0.20892427 +3 -3.5 -0.24473952 +3 -3 -0.14168225 +3 -2.5 0.058522603 +3 -2 0.30250825 +3 -1.5 0.53908657 +3 -1 0.72931647 +3 -0.5 0.85004375 +3 0 0.89112001 +3 0.5 0.85004375 +3 1 0.72931647 +3 1.5 0.53908657 +3 2 0.30250825 +3 2.5 0.058522603 +3 3 -0.14168225 +3 3.5 -0.24473952 +3 4 -0.20892427 +3 4.5 -0.017452726 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +4 -5 0.86965158 +4 -4.5 0.49061243 +4 -4 0.16382381 +4 -3.5 -0.073817185 +4 -3 -0.20892427 +4 -2.5 -0.24998941 +4 -2 -0.2212778 +4 -1.5 -0.15458671 +4 -1 -0.081339179 +4 -0.5 -0.026779756 +4 0 -0.0068024953 +4 0.5 -0.026779756 +4 1 -0.081339179 +4 1.5 -0.15458671 +4 2 -0.2212778 +4 2.5 -0.24998941 +4 3 -0.20892427 +4 3.5 -0.073817185 +4 4 0.16382381 +4 4.5 0.49061243 +}; +\addplot3 [draw=black, line width=0.18pt] +table {% +4.5 -5 1.1792179 +4.5 -4.5 0.83068791 +4.5 -4 0.49061243 +4.5 -3.5 0.20004682 +4.5 -3 -0.017452726 +4.5 -2.5 -0.1566904 +4.5 -2 -0.22760364 +4.5 -1.5 -0.24951869 +4.5 -1 -0.24473952 +4.5 -0.5 -0.23299205 +4.5 0 -0.22753012 +4.5 0.5 -0.23299205 +4.5 1 -0.24473952 +4.5 1.5 -0.24951869 +4.5 2 -0.22760364 +4.5 2.5 -0.1566904 +4.5 3 -0.017452726 +4.5 3.5 0.20004682 +4.5 4 0.49061243 +4.5 4.5 0.83068791 +}; +\addplot3 [draw=navy, line width=0.36pt] +table {% +-3.4591891 -5 -0.2 +-3.5 -4.9716164 -0.2 +-4 -4.5783396 -0.2 +-4.0873153 -4.5 -0.2 +-4.5 -4.0873153 -0.2 +-4.5783396 -4 -0.2 +-4.9716164 -3.5 -0.2 +-5 -3.4591891 -0.2 +}; +\addplot3 [draw=navy, line width=0.36pt] +table {% +4.5 -4.0873153 -0.2 +4.0873153 -4.5 -0.2 +4 -4.5783396 -0.2 +3.5 -4.9716164 -0.2 +3.4591891 -5 -0.2 +}; +\addplot3 [draw=navy, line width=0.36pt] +table {% +-5 3.4591891 -0.2 +-4.9716164 3.5 -0.2 +-4.5783396 4 -0.2 +-4.5 4.0873153 -0.2 +-4.0873153 4.5 -0.2 +}; +\addplot3 [draw=navy, line width=0.36pt] +table {% +4.0873153 4.5 -0.2 +4.5 4.0873153 -0.2 +}; +\addplot3 [draw=navy, line width=0.36pt] +table {% +-1 -3.1960206 -0.2 +-0.5 -3.3100429 -0.2 +0 -3.3467349 -0.2 +0.5 -3.3100429 -0.2 +1 -3.1960206 -0.2 +1.4713152 -3 -0.2 +1.5 -2.987459 -0.2 +2 -2.6807038 -0.2 +2.2162206 -2.5 -0.2 +2.5 -2.2162206 -0.2 +2.6807038 -2 -0.2 +2.987459 -1.5 -0.2 +3 -1.4713152 -0.2 +3.1960206 -1 -0.2 +3.3100429 -0.5 -0.2 +3.3467349 0 -0.2 +3.3100429 0.5 -0.2 +3.1960206 1 -0.2 +3 1.4713152 -0.2 +2.987459 1.5 -0.2 +2.6807038 2 -0.2 +2.5 2.2162206 -0.2 +2.2162206 2.5 -0.2 +2 2.6807038 -0.2 +1.5 2.987459 -0.2 +1.4713152 3 -0.2 +1 3.1960206 -0.2 +0.5 3.3100429 -0.2 +0 3.3467349 -0.2 +-0.5 3.3100429 -0.2 +-1 3.1960206 -0.2 +-1.4713152 3 -0.2 +-1.5 2.987459 -0.2 +-2 2.6807038 -0.2 +-2.2162206 2.5 -0.2 +-2.5 2.2162206 -0.2 +-2.6807038 2 -0.2 +-2.987459 1.5 -0.2 +-3 1.4713152 -0.2 +-3.1960206 1 -0.2 +-3.3100429 0.5 -0.2 +-3.3467349 0 -0.2 +-3.3100429 -0.5 -0.2 +-3.1960206 -1 -0.2 +-3 -1.4713152 -0.2 +-2.987459 -1.5 -0.2 +-2.6807038 -2 -0.2 +-2.5 -2.2162206 -0.2 +-2.2162206 -2.5 -0.2 +-2 -2.6807038 -0.2 +-1.5 -2.987459 -0.2 +-1.4713152 -3 -0.2 +}; +\addplot3 [draw=darkorange, line width=0.36pt] +table {% +-3.7996391 -5 0 +-4 -4.8421646 0 +-4.3813676 -4.5 0 +-4.5 -4.3813676 0 +-4.8421646 -4 0 +-5 -3.7996391 0 +}; +\addplot3 [draw=darkorange, line width=0.36pt] +table {% +4.5 -4.3813676 0 +4.3813676 -4.5 0 +4 -4.8421646 0 +3.7996391 -5 0 +}; +\addplot3 [draw=darkorange, line width=0.36pt] +table {% +-5 3.7996391 0 +-4.8421646 4 0 +-4.5 4.3813676 0 +-4.3813676 4.5 0 +}; +\addplot3 [draw=darkorange, line width=0.36pt] +table {% +4.3813676 4.5 0 +4.5 4.3813676 0 +}; +\addplot3 [draw=darkorange, line width=0.36pt] +table {% +-0.5 -3.1033778 0 +0 -3.1434429 0 +0.5 -3.1033778 0 +0.91433779 -3 0 +1 -2.9772587 0 +1.5 -2.7576311 0 +1.8945309 -2.5 0 +2 -2.4185697 0 +2.4185697 -2 0 +2.5 -1.8945309 0 +2.7576311 -1.5 0 +2.9772587 -1 0 +3 -0.91433779 0 +3.1033778 -0.5 0 +3.1434429 0 0 +3.1033778 0.5 0 +3 0.91433779 0 +2.9772587 1 0 +2.7576311 1.5 0 +2.5 1.8945309 0 +2.4185697 2 0 +2 2.4185697 0 +1.8945309 2.5 0 +1.5 2.7576311 0 +1 2.9772587 0 +0.91433779 3 0 +0.5 3.1033778 0 +0 3.1434429 0 +-0.5 3.1033778 0 +-0.91433779 3 0 +-1 2.9772587 0 +-1.5 2.7576311 0 +-1.8945309 2.5 0 +-2 2.4185697 0 +-2.4185697 2 0 +-2.5 1.8945309 0 +-2.7576311 1.5 0 +-2.9772587 1 0 +-3 0.91433779 0 +-3.1033778 0.5 0 +-3.1434429 0 0 +-3.1033778 -0.5 0 +-3 -0.91433779 0 +-2.9772587 -1 0 +-2.7576311 -1.5 0 +-2.5 -1.8945309 0 +-2.4185697 -2 0 +-2 -2.4185697 0 +-1.8945309 -2.5 0 +-1.5 -2.7576311 0 +-1 -2.9772587 0 +-0.91433779 -3 0 +}; +\addplot3 [draw=darkorange, line width=0.36pt] +table {% +0 0 0 +0 0 0 +0 0 0 +0 0 0 +}; +\addplot3 [draw=crimson, line width=0.36pt] +table {% +-4.1297758 -5 0.2 +-4.5 -4.6711647 0.2 +-4.6711647 -4.5 0.2 +-5 -4.1297758 0.2 +}; +\addplot3 [draw=crimson, line width=0.36pt] +table {% +4.5 -4.6711647 0.2 +4.1297758 -5 0.2 +}; +\addplot3 [draw=crimson, line width=0.36pt] +table {% +-5 4.1297758 0.2 +-4.6711647 4.5 0.2 +}; +\addplot3 [draw=crimson, line width=0.36pt] +table {% +-1.5 -2.5278032 0.2 +-1 -2.7573614 0.2 +-0.5 -2.8908886 0.2 +0 -2.9356295 0.2 +0.5 -2.8908886 0.2 +1 -2.7573614 0.2 +1.5 -2.5278032 0.2 +1.5425772 -2.5 0.2 +2 -2.1468345 0.2 +2.1468345 -2 0.2 +2.5 -1.5425772 0.2 +2.5278032 -1.5 0.2 +2.7573614 -1 0.2 +2.8908886 -0.5 0.2 +2.9356295 0 0.2 +2.8908886 0.5 0.2 +2.7573614 1 0.2 +2.5278032 1.5 0.2 +2.5 1.5425772 0.2 +2.1468345 2 0.2 +2 2.1468345 0.2 +1.5425772 2.5 0.2 +1.5 2.5278032 0.2 +1 2.7573614 0.2 +0.5 2.8908886 0.2 +0 2.9356295 0.2 +-0.5 2.8908886 0.2 +-1 2.7573614 0.2 +-1.5 2.5278032 0.2 +-1.5425772 2.5 0.2 +-2 2.1468345 0.2 +-2.1468345 2 0.2 +-2.5 1.5425772 0.2 +-2.5278032 1.5 0.2 +-2.7573614 1 0.2 +-2.8908886 0.5 0.2 +-2.9356295 0 0.2 +-2.8908886 -0.5 0.2 +-2.7573614 -1 0.2 +-2.5278032 -1.5 0.2 +-2.5 -1.5425772 0.2 +-2.1468345 -2 0.2 +-2 -2.1468345 0.2 +-1.5425772 -2.5 0.2 +}; +\addplot3 [draw=crimson, line width=0.36pt] +table {% +0 -0.20858296 0.2 +-0.20858296 0 0.2 +0 0.20858296 0.2 +0.20858296 0 0.2 +}; +\addplot3 [draw=indigo68184, line width=0.24pt] +table {% +-3.4591891 -5 -0.85 +-3.5 -4.9716164 -0.85 +-4 -4.5783396 -0.85 +-4.0873153 -4.5 -0.85 +-4.5 -4.0873153 -0.85 +-4.5783396 -4 -0.85 +-4.9716164 -3.5 -0.85 +-5 -3.4591891 -0.85 +}; +\addplot3 [draw=indigo68184, line width=0.24pt] +table {% +4.5 -4.0873153 -0.85 +4.0873153 -4.5 -0.85 +4 -4.5783396 -0.85 +3.5 -4.9716164 -0.85 +3.4591891 -5 -0.85 +}; +\addplot3 [draw=indigo68184, line width=0.24pt] +table {% +-5 3.4591891 -0.85 +-4.9716164 3.5 -0.85 +-4.5783396 4 -0.85 +-4.5 4.0873153 -0.85 +-4.0873153 4.5 -0.85 +}; +\addplot3 [draw=indigo68184, line width=0.24pt] +table {% +4.0873153 4.5 -0.85 +4.5 4.0873153 -0.85 +}; +\addplot3 [draw=indigo68184, line width=0.24pt] +table {% +-1 -3.1960206 -0.85 +-0.5 -3.3100429 -0.85 +0 -3.3467349 -0.85 +0.5 -3.3100429 -0.85 +1 -3.1960206 -0.85 +1.4713152 -3 -0.85 +1.5 -2.987459 -0.85 +2 -2.6807038 -0.85 +2.2162206 -2.5 -0.85 +2.5 -2.2162206 -0.85 +2.6807038 -2 -0.85 +2.987459 -1.5 -0.85 +3 -1.4713152 -0.85 +3.1960206 -1 -0.85 +3.3100429 -0.5 -0.85 +3.3467349 0 -0.85 +3.3100429 0.5 -0.85 +3.1960206 1 -0.85 +3 1.4713152 -0.85 +2.987459 1.5 -0.85 +2.6807038 2 -0.85 +2.5 2.2162206 -0.85 +2.2162206 2.5 -0.85 +2 2.6807038 -0.85 +1.5 2.987459 -0.85 +1.4713152 3 -0.85 +1 3.1960206 -0.85 +0.5 3.3100429 -0.85 +0 3.3467349 -0.85 +-0.5 3.3100429 -0.85 +-1 3.1960206 -0.85 +-1.4713152 3 -0.85 +-1.5 2.987459 -0.85 +-2 2.6807038 -0.85 +-2.2162206 2.5 -0.85 +-2.5 2.2162206 -0.85 +-2.6807038 2 -0.85 +-2.987459 1.5 -0.85 +-3 1.4713152 -0.85 +-3.1960206 1 -0.85 +-3.3100429 0.5 -0.85 +-3.3467349 0 -0.85 +-3.3100429 -0.5 -0.85 +-3.1960206 -1 -0.85 +-3 -1.4713152 -0.85 +-2.987459 -1.5 -0.85 +-2.6807038 -2 -0.85 +-2.5 -2.2162206 -0.85 +-2.2162206 -2.5 -0.85 +-2 -2.6807038 -0.85 +-1.5 -2.987459 -0.85 +-1.4713152 -3 -0.85 +}; +\addplot3 [draw=darkcyan32144140, line width=0.24pt] +table {% +-3.7996391 -5 -0.85 +-4 -4.8421646 -0.85 +-4.3813676 -4.5 -0.85 +-4.5 -4.3813676 -0.85 +-4.8421646 -4 -0.85 +-5 -3.7996391 -0.85 +}; +\addplot3 [draw=darkcyan32144140, line width=0.24pt] +table {% +4.5 -4.3813676 -0.85 +4.3813676 -4.5 -0.85 +4 -4.8421646 -0.85 +3.7996391 -5 -0.85 +}; +\addplot3 [draw=darkcyan32144140, line width=0.24pt] +table {% +-5 3.7996391 -0.85 +-4.8421646 4 -0.85 +-4.5 4.3813676 -0.85 +-4.3813676 4.5 -0.85 +}; +\addplot3 [draw=darkcyan32144140, line width=0.24pt] +table {% +4.3813676 4.5 -0.85 +4.5 4.3813676 -0.85 +}; +\addplot3 [draw=darkcyan32144140, line width=0.24pt] +table {% +-0.5 -3.1033778 -0.85 +0 -3.1434429 -0.85 +0.5 -3.1033778 -0.85 +0.91433779 -3 -0.85 +1 -2.9772587 -0.85 +1.5 -2.7576311 -0.85 +1.8945309 -2.5 -0.85 +2 -2.4185697 -0.85 +2.4185697 -2 -0.85 +2.5 -1.8945309 -0.85 +2.7576311 -1.5 -0.85 +2.9772587 -1 -0.85 +3 -0.91433779 -0.85 +3.1033778 -0.5 -0.85 +3.1434429 0 -0.85 +3.1033778 0.5 -0.85 +3 0.91433779 -0.85 +2.9772587 1 -0.85 +2.7576311 1.5 -0.85 +2.5 1.8945309 -0.85 +2.4185697 2 -0.85 +2 2.4185697 -0.85 +1.8945309 2.5 -0.85 +1.5 2.7576311 -0.85 +1 2.9772587 -0.85 +0.91433779 3 -0.85 +0.5 3.1033778 -0.85 +0 3.1434429 -0.85 +-0.5 3.1033778 -0.85 +-0.91433779 3 -0.85 +-1 2.9772587 -0.85 +-1.5 2.7576311 -0.85 +-1.8945309 2.5 -0.85 +-2 2.4185697 -0.85 +-2.4185697 2 -0.85 +-2.5 1.8945309 -0.85 +-2.7576311 1.5 -0.85 +-2.9772587 1 -0.85 +-3 0.91433779 -0.85 +-3.1033778 0.5 -0.85 +-3.1434429 0 -0.85 +-3.1033778 -0.5 -0.85 +-3 -0.91433779 -0.85 +-2.9772587 -1 -0.85 +-2.7576311 -1.5 -0.85 +-2.5 -1.8945309 -0.85 +-2.4185697 -2 -0.85 +-2 -2.4185697 -0.85 +-1.8945309 -2.5 -0.85 +-1.5 -2.7576311 -0.85 +-1 -2.9772587 -0.85 +-0.91433779 -3 -0.85 +}; +\addplot3 [draw=darkcyan32144140, line width=0.24pt] +table {% +0 0 -0.85 +0 0 -0.85 +0 0 -0.85 +0 0 -0.85 +}; +\addplot3 [draw=gold25323136, line width=0.24pt] +table {% +-4.1297758 -5 -0.85 +-4.5 -4.6711647 -0.85 +-4.6711647 -4.5 -0.85 +-5 -4.1297758 -0.85 +}; +\addplot3 [draw=gold25323136, line width=0.24pt] +table {% +4.5 -4.6711647 -0.85 +4.1297758 -5 -0.85 +}; +\addplot3 [draw=gold25323136, line width=0.24pt] +table {% +-5 4.1297758 -0.85 +-4.6711647 4.5 -0.85 +}; +\addplot3 [draw=gold25323136, line width=0.24pt] +table {% +-1.5 -2.5278032 -0.85 +-1 -2.7573614 -0.85 +-0.5 -2.8908886 -0.85 +0 -2.9356295 -0.85 +0.5 -2.8908886 -0.85 +1 -2.7573614 -0.85 +1.5 -2.5278032 -0.85 +1.5425772 -2.5 -0.85 +2 -2.1468345 -0.85 +2.1468345 -2 -0.85 +2.5 -1.5425772 -0.85 +2.5278032 -1.5 -0.85 +2.7573614 -1 -0.85 +2.8908886 -0.5 -0.85 +2.9356295 0 -0.85 +2.8908886 0.5 -0.85 +2.7573614 1 -0.85 +2.5278032 1.5 -0.85 +2.5 1.5425772 -0.85 +2.1468345 2 -0.85 +2 2.1468345 -0.85 +1.5425772 2.5 -0.85 +1.5 2.5278032 -0.85 +1 2.7573614 -0.85 +0.5 2.8908886 -0.85 +0 2.9356295 -0.85 +-0.5 2.8908886 -0.85 +-1 2.7573614 -0.85 +-1.5 2.5278032 -0.85 +-1.5425772 2.5 -0.85 +-2 2.1468345 -0.85 +-2.1468345 2 -0.85 +-2.5 1.5425772 -0.85 +-2.5278032 1.5 -0.85 +-2.7573614 1 -0.85 +-2.8908886 0.5 -0.85 +-2.9356295 0 -0.85 +-2.8908886 -0.5 -0.85 +-2.7573614 -1 -0.85 +-2.5278032 -1.5 -0.85 +-2.5 -1.5425772 -0.85 +-2.1468345 -2 -0.85 +-2 -2.1468345 -0.85 +-1.5425772 -2.5 -0.85 +}; +\addplot3 [draw=gold25323136, line width=0.24pt] +table {% +0 -0.20858296 -0.85 +-0.20858296 0 -0.85 +0 0.20858296 -0.85 +0.20858296 0 -0.85 +}; +\end{axis} + +\end{tikzpicture} diff --git a/tests/test_escape_chars.py b/tests/test_escape_chars.py index 70c31422..edaccabd 100644 --- a/tests/test_escape_chars.py +++ b/tests/test_escape_chars.py @@ -3,11 +3,13 @@ https://github.com/nschloe/tikzplotlib/issues/332 """ +from collections.abc import Callable + import matplotlib as mpl import matplotlib.pyplot as plt from matplotlib.figure import Figure -from .helpers import assert_equality +import matplot2tikz mpl.use("Agg") @@ -21,5 +23,66 @@ def plot() -> Figure: return fig -def test() -> None: - assert_equality(plot, "test_escape_chars_reference.tex") +def _get_tikz_code(plot_func: Callable[[], Figure]) -> str: + plot_func() + code = matplot2tikz.get_tikz_code( + include_disclaimer=False, + float_format=".8g", + ) + plt.close("all") + return code + + +def test_text_mode_escaping() -> None: + code = _get_tikz_code(plot) + escaped = "Foo \\& Bar Dogs\\_N\\_Cats \\%" + assert f"title={{{escaped}}}" in code + assert f"xlabel={{{escaped}}}" in code + assert f"ylabel={{{escaped}}}" in code + + +def plot_math_label() -> Figure: + fig = plt.figure() + plt.plot([0.1, 10.0], [0.0, 1.0]) + plt.xscale("log") + plt.xlabel(r"$\\log_{10} \\mathrm{X}$") + return fig + + +def test_math_mode_underscore_not_escaped() -> None: + code = _get_tikz_code(plot_math_label) + xlabel_line = next(line for line in code.splitlines() if line.strip().startswith("xlabel=")) + assert r"\\log_{10} \\mathrm{X}" in xlabel_line + assert r"\\log\\_{10}" not in xlabel_line + + +def plot_escaped_dollar() -> Figure: + fig = plt.figure() + plt.plot([0.0, 1.0], [0.0, 1.0]) + plt.xlabel(r"$\text{Price } \${}100 \approx 86\,\text{\euro}_{\text{in mai 2026}}$") + return fig + + +def test_escaped_dollar_is_literal() -> None: + code = _get_tikz_code(plot_escaped_dollar) + xlabel_line = next(line for line in code.splitlines() if line.strip().startswith("xlabel=")) + assert ( + r"\(\displaystyle \text{Price } \${}100 \approx 86\," + r"\text{\euro}_{\text{in mai 2026}}\)" in xlabel_line + ) + + +def plot_multiple_math_segments() -> Figure: + fig = plt.figure() + plt.plot([0.0, 1.0], [0.0, 1.0]) + plt.xlabel(r"gain $x_1$ vs $x_2$") + return fig + + +def test_multiple_math_segments() -> None: + code = _get_tikz_code(plot_multiple_math_segments) + xlabel_line = next(line for line in code.splitlines() if line.strip().startswith("xlabel=")) + assert r"\(\displaystyle x_1\)" in xlabel_line + assert r"\(\displaystyle x_2\)" in xlabel_line + assert r"x\_1" not in xlabel_line + assert r"x\_2" not in xlabel_line diff --git a/tests/test_externalize_tables_reference.tex b/tests/test_externalize_tables_reference.tex index b91a5021..71dcc9df 100644 --- a/tests/test_externalize_tables_reference.tex +++ b/tests/test_externalize_tables_reference.tex @@ -23,9 +23,9 @@ ytick style={color=dimgray85} ] \addplot [semithick, greenyellow17025576, mark=*, mark size=3, mark options={solid}] -table {}{tmp-000.dat}; +table {tmp-000.dat}; \addplot [very thick, chocolate2267451, opacity=0.3, mark=*, mark size=3, mark options={solid}] -table {}{tmp-001.dat}; +table {tmp-001.dat}; \end{axis} \end{tikzpicture} diff --git a/tests/test_scatter_colormap_reference.tex b/tests/test_scatter_colormap_reference.tex index 1b7bd50c..040118c4 100644 --- a/tests/test_scatter_colormap_reference.tex +++ b/tests/test_scatter_colormap_reference.tex @@ -1,6 +1,7 @@ \begin{tikzpicture} \definecolor{darkgray176}{RGB}{176,176,176} +\definecolor{steelblue31119180}{RGB}{31,119,180} \begin{axis}[ tick align=outside, @@ -14,6 +15,8 @@ ] \addplot [ colormap/viridis, + draw=steelblue31119180, + mark=*, only marks, scatter, scatter src=explicit, @@ -22,16 +25,16 @@ ] table [x=x, y=y, meta=colordata]{% x y colordata sizedata --0.98912135 0.097167319 -0.3617668721609232 3.133157971456955 --0.36778665 -1.5259304 -1.230232195490445 2.817072624262411 -1.2879253 1.1921661 1.2262292928211507 4.1765658564645 -0.19397442 -0.67108968 -2.1720438866851817 4.458355091802991 -0.9202309 1.0002694 -0.37014734585231535 2.909294279310992 -0.57710379 0.13632112 0.16438006967466792 1.8999491559296784 --0.63646365 1.5320331 0.8598811846127368 4.08412434479352 -0.54195222 -0.65996941 1.761661236511811 4.903644914098962 --0.31659545 -0.31179486 0.993323775951811 5.431304123364008 --0.32238912 0.33776913 -0.29152142609843873 4.593835880515875 +-0.98912135 0.097167319 -0.36176687 3.133158 +-0.36778665 -1.5259304 -1.2302322 2.8170726 +1.2879253 1.1921661 1.2262293 4.1765659 +0.19397442 -0.67108968 -2.1720439 4.4583551 +0.9202309 1.0002694 -0.37014735 2.9092943 +0.57710379 0.13632112 0.16438007 1.8999492 +-0.63646365 1.5320331 0.85988118 4.0841243 +0.54195222 -0.65996941 1.7616612 4.9036449 +-0.31659545 -0.31179486 0.99332378 5.4313041 +-0.32238912 0.33776913 -0.29152143 4.5938359 }; \end{axis} diff --git a/tests/test_scatter_different_sizes_reference.tex b/tests/test_scatter_different_sizes_reference.tex index 0ad33694..aa51801c 100644 --- a/tests/test_scatter_different_sizes_reference.tex +++ b/tests/test_scatter_different_sizes_reference.tex @@ -22,9 +22,9 @@ ] table{% x y sizedata -1 5 9.772050238058398 -2 7 9.772050238058398 -3 1 9.772050238058398 +1 5 9.7720502 +2 7 9.7720502 +3 1 9.7720502 }; \end{axis}