tikzplotly 0.2.1__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/PKG-INFO +2 -1
  2. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/pyproject.toml +10 -2
  3. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly/__about__.py +1 -1
  4. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly/_color.py +10 -9
  5. tikzplotly-0.2.1/src/tikzplotly/_dataContainer.py → tikzplotly-0.2.2/src/tikzplotly/_data_container.py +1 -1
  6. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly/_heatmap.py +3 -2
  7. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly/_histogram.py +27 -27
  8. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly/_polar.py +1 -1
  9. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly/_save.py +4 -4
  10. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly/_scatter.py +6 -36
  11. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly/_scatter3d.py +6 -35
  12. tikzplotly-0.2.2/src/tikzplotly/_trace_utils.py +70 -0
  13. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly/_utils.py +2 -10
  14. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly.egg-info/PKG-INFO +2 -1
  15. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly.egg-info/SOURCES.txt +3 -1
  16. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly.egg-info/requires.txt +1 -0
  17. tikzplotly-0.2.2/tests/test_colors.py +53 -0
  18. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/tests/test_heatmap.py +9 -5
  19. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/tests/test_histograms.py +10 -3
  20. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/tests/test_markers.py +7 -1
  21. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/tests/test_polar.py +3 -1
  22. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/tests/test_scatter.py +8 -6
  23. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/tests/test_scatter3d.py +10 -2
  24. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/tests/test_specific.py +16 -12
  25. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/LICENSE +0 -0
  26. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/README.md +0 -0
  27. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/setup.cfg +0 -0
  28. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tests/test_bars.py +0 -0
  29. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tests/test_heatmaps.py +0 -0
  30. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tests/test_histograms.py +0 -0
  31. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tests/test_line_charts.py +0 -0
  32. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tests/test_markers.py +0 -0
  33. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tests/test_polar.py +0 -0
  34. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tests/test_scatter3d.py +0 -0
  35. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly/__init__.py +0 -0
  36. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly/_annotations.py +0 -0
  37. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly/_axis.py +0 -0
  38. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly/_bar.py +0 -0
  39. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly/_dash.py +0 -0
  40. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly/_data.py +0 -0
  41. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly/_marker.py +0 -0
  42. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly/_tex.py +0 -0
  43. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly.egg-info/dependency_links.txt +0 -0
  44. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/src/tikzplotly.egg-info/top_level.txt +0 -0
  45. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/tests/test_bars.py +0 -0
  46. {tikzplotly-0.2.1 → tikzplotly-0.2.2}/tests/test_tikzplotly.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tikzplotly
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Convert plotly figures to LaTeX / TikZ figures
5
5
  Author-email: Thomas Saigre <tikzplotly@outlook.fr>
6
6
  License: The MIT License (MIT)
@@ -44,6 +44,7 @@ License-File: LICENSE
44
44
  Requires-Dist: numpy
45
45
  Requires-Dist: plotly
46
46
  Requires-Dist: pillow
47
+ Requires-Dist: kaleido
47
48
  Provides-Extra: dev
48
49
  Requires-Dist: pandas; extra == "dev"
49
50
  Dynamic: license-file
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tikzplotly"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "Convert plotly figures to LaTeX / TikZ figures"
9
9
  readme = "README.md"
10
10
  authors = [{name = "Thomas Saigre", email = "tikzplotly@outlook.fr"}]
@@ -27,7 +27,8 @@ keywords = ["plotly", "latex", "tikz", "figure", "conversion", "graphics"]
27
27
  dependencies = [
28
28
  "numpy",
29
29
  "plotly",
30
- "pillow"
30
+ "pillow",
31
+ "kaleido"
31
32
  ]
32
33
 
33
34
  [project.optional-dependencies]
@@ -36,3 +37,10 @@ dev = ["pandas"]
36
37
  [project.urls]
37
38
  Code = "https://github.com/thomas-saigre/tikzplotly"
38
39
  Issues = "https://github.com/thomas-saigre/tikzplotly/issues"
40
+
41
+
42
+ [tool.pytest.ini_options]
43
+ filterwarnings = [
44
+ "ignore:png image has not been reduced.*:UserWarning",
45
+ "ignore:Text template is not supported yet.:UserWarning",
46
+ ]
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Module metadata for tikzplotly.
3
3
  """
4
- __version__ = "0.2.1"
4
+ __version__ = "0.2.2"
5
5
  __author__ = "Thomas Saigre"
6
6
  __license__ = "MIT"
7
7
  __description__ = "Convert plotly figures to TikZ code for inclusion into PGFPlots figures."
@@ -30,7 +30,7 @@ def convert_color(color):
30
30
  string from plotly color palette, can be one of the following:
31
31
  - hex string : "#______"
32
32
  - rgb string : "rgb(__, __, __)" with values between 0 and 255
33
- - rgba string : "rgba(__, __, __, __)" with values between 0 and 255 and an opacity value between 0 and 1
33
+ - rgba string : "rgba(__, __, __[, __])" with values between 0 and 255 and an opacity value between 0 and 1 (optional)
34
34
  - color name : "red", "green", "blue", "yellow", "orange", "purple", "brown", "black", "gray", "white" or any other color name from
35
35
  https://github.com/plotly/plotly.py/blob/main/templategen/utils/colors.py
36
36
 
@@ -45,20 +45,21 @@ def convert_color(color):
45
45
  if color is None:
46
46
  return None, None, None, 1
47
47
  if isinstance(color, numpy.ndarray):
48
- warn("Color from data is not supported yet. Returning the default color: blue.")
49
- return "blue", "HTML", "0000ff", 1
48
+ warn("Color from data is not supported yet. Returning no color.")
49
+ return None, None, None, 1
50
50
  if not isinstance(color, str):
51
- warn(f"Color {color} type '{color.__class__.__name__}' is not supported yet. Returning the default color: blue.")
52
- return "blue", "HTML", "0000ff", 1
51
+ warn(f"Color {color} type '{color.__class__.__name__}' is not supported yet. Returning no color.")
52
+ return None, None, None, 1
53
53
 
54
54
  if color.startswith("#"):
55
55
  return color[1:], "HTML", color[1:], 1
56
56
 
57
57
  if color.startswith("rgba"):
58
- sp = color.split("(")[1].split(",")
59
- rgb_color = sp[:-1]
60
- rgb_color = convert_color(f"rgb({rgb_color[0]},{rgb_color[1]},{rgb_color[2]})")[:-1]
61
- return rgb_color + (float(sp[-1][:-1]),)
58
+ sp = color.split("(")[1].replace(')', '').split(",")
59
+ rgb_color = [s.strip() for s in sp]
60
+ r, g, b = int(rgb_color[0]), int(rgb_color[1]), int(rgb_color[2])
61
+ opacity = float(rgb_color[3]) if len(rgb_color) == 4 else 1.0
62
+ return hashlib.sha1(f"{r}, {g}, {b}".encode('UTF-8')).hexdigest()[:10], "RGB", rgb_str(r, g, b), opacity
62
63
 
63
64
  if color.startswith("rgb"):
64
65
  color = color[4:-1].replace("[", "{").replace("]", "}")
@@ -186,7 +186,7 @@ class DataContainer:
186
186
  return data.name, data.z_name
187
187
  data_obj = Data3D(x, y, z, name)
188
188
  self.data.append(data_obj)
189
- return data_obj.name, data_obj.z_name
189
+ return data_obj.name
190
190
 
191
191
  def export_data(self):
192
192
  """Generate LaTeX code to export the data from DataContainer.
@@ -102,9 +102,10 @@ def draw_heatmap(data, fig, img_name, axis: Axis):
102
102
 
103
103
  img_bytes = fig_copy.to_image(format="png") # The image created by plotly keeps places around the heatmap
104
104
  cropped_image = crop_image(Image.open(io.BytesIO(img_bytes))) # so we crop all the white around the figure
105
- resized_image = resize_image(cropped_image, *figure_data.shape) # and we resize it so each square is a 1px x 1px square
105
+ resized_image = resize_image(cropped_image, *figure_data.shape) # and we resize it so each square is a 1px x 1px square
106
106
 
107
- os.makedirs(os.path.dirname(img_name), exist_ok=True) # Make sure the directory exists
107
+ dirname = os.path.dirname(img_name) or '.' # Ensure that dirname is not empty
108
+ os.makedirs(dirname, exist_ok=True) # Make sure the directory exists
108
109
  resized_image.save(img_name)
109
110
 
110
111
 
@@ -50,6 +50,27 @@ def formalize_data(data, axis:Axis, row_sep="\\\\"):
50
50
 
51
51
  return data_str
52
52
 
53
+ def treat_histnorm(histnorm: str, hist_options: dict):
54
+ """Handle the normalization option of the histogram
55
+
56
+ Parameters
57
+ ----------
58
+ histnorm : str
59
+ The normalization mode for the histogram.
60
+ hist_options : dict
61
+ Dictionary to store histogram options.
62
+ """
63
+ if histnorm in ("percent", "probability", "density"):
64
+ warn(
65
+ f"Sorry, I did not find an equivalent for histnorm='{histnorm}' in TikZ. "
66
+ "If you need this feature implemented, please open an issue, if possible with a MWE pgfplots code "
67
+ "that would plot this :).\nFor now, the histogram will be plotted without normalization "
68
+ "(as if histnorm='probability density')."
69
+ )
70
+ hist_options["density"] = None
71
+ elif histnorm in ("probability density", ):
72
+ hist_options["density"] = None
73
+
53
74
  def draw_histogram(trace, axis: Axis, colors_set, row_sep="\\\\"):
54
75
  """
55
76
  Draw a histogram and return the TikZ code.
@@ -79,7 +100,8 @@ def draw_histogram(trace, axis: Axis, colors_set, row_sep="\\\\"):
79
100
 
80
101
  code = ""
81
102
 
82
- plot_options = {"hist": None}
103
+ plot_options = {}
104
+ plot_options["hist"] = None
83
105
  type_options = {"row sep": row_sep, "y index": 0}
84
106
  hist_options = {}
85
107
 
@@ -92,36 +114,15 @@ def draw_histogram(trace, axis: Axis, colors_set, row_sep="\\\\"):
92
114
  axis.add_option("x filter/.expression", "rawy")
93
115
  axis.add_option("y filter/.expression", "rawx")
94
116
  hist_options["handler/.style"] = "{xbar interval}"
95
-
117
+ else: # trace.x and trace.y are both empty
118
+ warn("Empty histogram. Returning no plot")
119
+ return ""
96
120
 
97
121
 
98
122
  if trace.nbinsx is not None:
99
123
  hist_options["bins"] = trace.nbinsx
100
124
 
101
- if trace.histnorm == "percent":
102
- warn(
103
- f"Sorry, I did not find an equivalent for histnorm='{trace.histnorm}' in TikZ. "
104
- "If you need this feature implemented, please open an issue, if possible with a MWE pgfplots code "
105
- "that would plot this :).\nFor now, the histogram will be plotted without normalization "
106
- "(as if histnorm='probability density')."
107
- )
108
- hist_options["density"] = None
109
- elif trace.histnorm == "probability":
110
- warn(
111
- f"Sorry, I did not find an equivalent for histnorm='{trace.histnorm}' in TikZ. "
112
- "If you need this feature implemented, please open an issue, if possible with a MWE pgfplots code that would plot this :).\n"
113
- "For now, the histogram will be plotted without normalization (as if histnorm='probability density')."
114
- )
115
- hist_options["density"] = None
116
- elif trace.histnorm == "density":
117
- warn(
118
- f"Sorry, I did not find an equivalent for histnorm='{trace.histnorm}' in TikZ. "
119
- "If you need this feature implemented, please open an issue, if possible with a MWE pgfplots code that would plot this :).\n"
120
- "For now, the histogram will be plotted without normalization (as if histnorm='probability density')."
121
- )
122
- hist_options["density"] = None
123
- elif trace.histnorm == "probability density":
124
- hist_options["density"] = None
125
+ treat_histnorm(trace.histnorm, hist_options)
125
126
 
126
127
  if trace.cumulative.enabled:
127
128
  hist_options["cumulative"] = None
@@ -152,5 +153,4 @@ def draw_histogram(trace, axis: Axis, colors_set, row_sep="\\\\"):
152
153
  code += tex_addplot(data_str, plot_type = "table",
153
154
  options = option_dict_to_str(plot_options), type_options=option_dict_to_str(type_options))
154
155
 
155
-
156
156
  return code
@@ -7,7 +7,7 @@ from ._axis import Axis
7
7
  from ._utils import option_dict_to_str
8
8
  from ._tex import tex_addplot
9
9
  from ._color import convert_color
10
- from ._dataContainer import DataContainer
10
+ from ._data_container import DataContainer
11
11
 
12
12
  def get_polar_coord(trace, axis: Axis, data_container: DataContainer):
13
13
  """Get polar coordinates from the trace
@@ -19,7 +19,7 @@ from ._polar import get_polar_coord, draw_scatterpolar
19
19
  from ._axis import Axis
20
20
  from ._color import convert_color
21
21
  from ._annotations import str_from_annotation
22
- from ._dataContainer import DataContainer
22
+ from ._data_container import DataContainer
23
23
  from ._utils import sanitize_tex_text, sanitize_text
24
24
 
25
25
 
@@ -78,13 +78,13 @@ def get_tikz_code(
78
78
 
79
79
  # If x is textual => symbolic x coords
80
80
  if all(isinstance(v, str) for v in trace.x):
81
- sanitized_trace_x = [sanitize_text(x, keep_space=-1) for x in trace.x]
81
+ sanitized_trace_x = [sanitize_text(str(x), keep_space=-1) for x in trace.x]
82
82
  axis.add_option("symbolic x coords", "{" + ",".join(sanitized_trace_x) + "}")
83
83
  axis.add_option("xtick", "data")
84
84
 
85
85
  # If y is textual => symbolic y coords
86
86
  if all(isinstance(v, str) for v in trace.y):
87
- sanitized_trace_y = [sanitize_text(y, keep_space=-1) for y in trace.y]
87
+ sanitized_trace_y = [sanitize_text(str(y), keep_space=-1) for y in trace.y]
88
88
  axis.add_option("symbolic y coords", "{" + ",".join(sanitized_trace_y) + "}")
89
89
  axis.add_option("ytick", "data")
90
90
 
@@ -173,7 +173,7 @@ def get_tikz_code(
173
173
  if hasattr(figure_layout.scene, "title") and getattr(figure_layout.scene.title, "text", None):
174
174
  axis.add_option("title", f"{{{sanitize_tex_text(figure_layout.scene.title.text)}}}")
175
175
 
176
- data_name_macro, z_name = data_container.add_data3d(trace.x, trace.y, trace.z, trace.name)
176
+ data_name_macro = data_container.add_data3d(trace.x, trace.y, trace.z, trace.name)
177
177
  data_str.append(draw_scatter3d(data_name_macro, trace, colors_set))
178
178
 
179
179
  if trace.name and trace['showlegend'] is not False:
@@ -2,16 +2,16 @@
2
2
  Provides functionality to convert Plotly scatter traces into TikZ/PGFPlots code for LaTeX documents.
3
3
  """
4
4
 
5
- from warnings import warn
6
5
  import numpy as np
7
6
  from ._tex import tex_addplot, tex_add_text
8
7
  from ._color import convert_color
9
- from ._marker import marker_symbol_to_tex
10
8
  from ._dash import DASH_PATTERN
11
9
  from ._axis import Axis
12
10
  from ._data import data_type
11
+ from ._trace_utils import configure_marker_options, finalize_marker_options
13
12
  from ._utils import px_to_pt, option_dict_to_str
14
13
 
14
+
15
15
  def draw_scatter2d(data_name, scatter, y_name, axis: Axis, color_set):
16
16
  """Get code for a scatter trace.
17
17
 
@@ -53,15 +53,8 @@ def draw_scatter2d(data_name, scatter, y_name, axis: Axis, color_set):
53
53
  options_dict = {}
54
54
  mark_option_dict = {}
55
55
 
56
- if mode == "markers":
57
- if marker.symbol is not None:
58
- symbol, symbol_options = marker_symbol_to_tex(marker.symbol)
59
- options_dict["mark"] = symbol
60
- options_dict["only marks"] = None
61
- if symbol_options is not None:
62
- mark_option_dict[symbol_options[0]] = symbol_options[1]
63
- else:
64
- options_dict["only marks"] = None
56
+ if "markers" in mode:
57
+ configure_marker_options(mode, marker, options_dict, mark_option_dict, color_set)
65
58
 
66
59
  if scatter.marker.size is not None:
67
60
  options_dict["mark size"] = px_to_pt(marker.size)
@@ -71,14 +64,7 @@ def draw_scatter2d(data_name, scatter, y_name, axis: Axis, color_set):
71
64
  mark_option_dict["solid"] = None
72
65
  mark_option_dict["fill"] = convert_color(scatter.marker.color)[0]
73
66
 
74
- if (line:=scatter.marker.line) is not None:
75
- if line.color is not None:
76
- color_set.add(convert_color(line.color)[:3])
77
- mark_option_dict["draw"] = convert_color(line.color)[0]
78
- if line.width is not None:
79
- mark_option_dict["line width"] = px_to_pt(line.width)
80
-
81
- if (angle:=scatter.marker.angle) is not None:
67
+ if (angle := scatter.marker.angle) is not None:
82
68
  mark_option_dict["rotate"] = angle
83
69
 
84
70
  if (opacity := scatter.opacity) is not None:
@@ -86,22 +72,7 @@ def draw_scatter2d(data_name, scatter, y_name, axis: Axis, color_set):
86
72
  if (opacity := scatter.marker.opacity) is not None:
87
73
  mark_option_dict["opacity"] = np.round(opacity, 2)
88
74
 
89
- if mark_option_dict:
90
- mark_options = option_dict_to_str(mark_option_dict)
91
- options_dict["mark options"] = f"{{{mark_options}}}"
92
-
93
- elif mode == "lines":
94
- options_dict["mark"] = "none"
95
-
96
- elif "lines" in mode and "markers" in mode:
97
- if marker.symbol is not None:
98
- symbol, symbol_options = marker_symbol_to_tex(marker.symbol)
99
- options_dict["mark"] = symbol
100
- if symbol_options is not None:
101
- mark_option_dict[symbol_options[0]] = symbol_options[1]
102
-
103
- else:
104
- warn(f"Scatter : Mode {mode} is not supported yet.")
75
+ finalize_marker_options(mode, options_dict, mark_option_dict, "Scatter")
105
76
 
106
77
  if scatter.line.width is not None:
107
78
  options_dict["line width"] = px_to_pt(scatter.line.width)
@@ -110,7 +81,6 @@ def draw_scatter2d(data_name, scatter, y_name, axis: Axis, color_set):
110
81
  if scatter.connectgaps in [False, None] and None in scatter.x:
111
82
  options_dict["unbounded coords"] = "jump"
112
83
 
113
-
114
84
  if scatter.line.color is not None:
115
85
  options_dict["color"] = convert_color(scatter.line.color)[0]
116
86
  if "mark" in mode:
@@ -1,12 +1,13 @@
1
1
  """
2
2
  Provides functionality to convert Plotly 3D scatter traces into TikZ/PGFPlots code for LaTeX documents.
3
3
  """
4
- from warnings import warn
4
+
5
5
  import numpy as np
6
6
  from ._color import convert_color
7
- from ._marker import marker_symbol_to_tex
7
+ from ._trace_utils import configure_marker_options, finalize_marker_options
8
8
  from ._utils import px_to_pt, option_dict_to_str
9
9
 
10
+
10
11
  def draw_scatter3d(data_name, scatter, color_set):
11
12
  """
12
13
  Get code for a scatter3d trace.
@@ -28,16 +29,8 @@ def draw_scatter3d(data_name, scatter, color_set):
28
29
  options_dict = {}
29
30
  mark_option_dict = {}
30
31
 
31
- # Markers only
32
- if mode == "markers":
33
- if marker.symbol is not None:
34
- symbol, symbol_options = marker_symbol_to_tex(marker.symbol)
35
- options_dict["mark"] = symbol
36
- options_dict["only marks"] = None
37
- if symbol_options is not None:
38
- mark_option_dict[symbol_options[0]] = symbol_options[1]
39
- else:
40
- options_dict["only marks"] = None
32
+ if "markers" in mode:
33
+ configure_marker_options(mode, marker, options_dict, mark_option_dict, color_set)
41
34
 
42
35
  if marker.size is not None:
43
36
  size = marker.size
@@ -53,34 +46,12 @@ def draw_scatter3d(data_name, scatter, color_set):
53
46
  mark_option_dict["solid"] = None
54
47
  mark_option_dict["fill"] = convert_color(c)[0]
55
48
 
56
- if (line := marker.line) is not None:
57
- if line.color is not None:
58
- color_set.add(convert_color(line.color)[:3])
59
- mark_option_dict["draw"] = convert_color(line.color)[0]
60
- if line.width is not None:
61
- mark_option_dict["line width"] = px_to_pt(line.width)
62
-
63
49
  if (opacity := scatter.opacity) is not None:
64
50
  options_dict["opacity"] = opacity
65
51
  if (opacity := marker.opacity) is not None:
66
52
  mark_option_dict["opacity"] = opacity
67
53
 
68
- if mark_option_dict:
69
- mark_options = option_dict_to_str(mark_option_dict)
70
- options_dict["mark options"] = f"{{{mark_options}}}"
71
-
72
- elif mode == "lines":
73
- options_dict["mark"] = "none"
74
-
75
- elif "lines" in mode and "markers" in mode:
76
- if marker.symbol is not None:
77
- symbol, symbol_options = marker_symbol_to_tex(marker.symbol)
78
- options_dict["mark"] = symbol
79
- if symbol_options is not None:
80
- mark_option_dict[symbol_options[0]] = symbol_options[1]
81
-
82
- else:
83
- warn(f"Scatter3d : Mode {mode} is not supported yet.")
54
+ finalize_marker_options(mode, options_dict, mark_option_dict, "Scatter3d")
84
55
 
85
56
  if scatter.line is not None:
86
57
  if scatter.line.width is not None:
@@ -0,0 +1,70 @@
1
+ """Shared helpers for Plotly trace to TikZ conversion."""
2
+
3
+ from warnings import warn
4
+
5
+ from ._color import convert_color
6
+ from ._marker import marker_symbol_to_tex
7
+ from ._utils import px_to_pt, option_dict_to_str
8
+
9
+
10
+ def configure_marker_options(mode, marker, options_dict, mark_option_dict, color_set):
11
+ """Populate marker-related TikZ options for scatter-like traces.
12
+
13
+ Parameters
14
+ ----------
15
+ mode
16
+ Plotly trace mode string (for example ``markers`` or ``markers+lines``).
17
+ marker
18
+ Plotly marker object from the trace.
19
+ options_dict
20
+ Dictionary collecting top-level PGFPlots options.
21
+ mark_option_dict
22
+ Dictionary collecting nested ``mark options`` for PGFPlots.
23
+ color_set
24
+ Set used to track colors that must be defined in the LaTeX output.
25
+ """
26
+
27
+ if marker.symbol is not None:
28
+ symbol, symbol_options = marker_symbol_to_tex(marker.symbol)
29
+ options_dict["mark"] = symbol
30
+ if "lines" not in mode:
31
+ options_dict["only marks"] = None
32
+ if symbol_options is not None:
33
+ mark_option_dict[symbol_options[0]] = symbol_options[1]
34
+ elif "lines" not in mode:
35
+ options_dict["only marks"] = None
36
+
37
+ if (line := marker.line) is not None:
38
+ if line.color is not None:
39
+ color = convert_color(line.color)
40
+ color_set.add(color[:3])
41
+ mark_option_dict["draw"] = color[0]
42
+ if line.width is not None:
43
+ mark_option_dict["line width"] = px_to_pt(line.width)
44
+
45
+
46
+ def finalize_marker_options(mode, options_dict, mark_option_dict, trace_name):
47
+ """Finalize marker options and handle unsupported modes.
48
+
49
+ Parameters
50
+ ----------
51
+ mode
52
+ Plotly trace mode string.
53
+ options_dict
54
+ Dictionary collecting top-level PGFPlots options.
55
+ mark_option_dict
56
+ Dictionary collecting nested ``mark options`` for PGFPlots.
57
+ trace_name
58
+ Name used in warnings to identify the trace kind.
59
+ """
60
+
61
+ if "markers" in mode:
62
+ if mark_option_dict:
63
+ mark_options = option_dict_to_str(mark_option_dict)
64
+ options_dict["mark options"] = f"{{{mark_options}}}"
65
+ return
66
+
67
+ if mode == "lines":
68
+ options_dict["mark"] = "none"
69
+ else:
70
+ warn(f"{trace_name} : Mode {mode} is not supported yet.")
@@ -93,18 +93,10 @@ def sanitize_char(ch: str, keep_space: int = 0) -> str:
93
93
  if ch == "@":
94
94
  return "at"
95
95
  if ch == " ":
96
- if keep_space == 1:
97
- return " "
98
- if keep_space == 0:
99
- return "_"
100
- return ""
96
+ return " " if keep_space == 1 else "_" if keep_space == 0 else ""
101
97
  if ch in "[]{}= ":
102
98
  return f"x{ord(ch):x}"
103
- # if not ascii, return hex
104
- if ord(ch) > 127:
105
- return f"x{ord(ch):x}"
106
- # if not printable, return hex
107
- if not ch.isprintable():
99
+ if ord(ch) > 127 or not ch.isprintable(): # not ascii or not printable: return hex
108
100
  return f"x{ord(ch):x}"
109
101
  return ch
110
102
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tikzplotly
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Convert plotly figures to LaTeX / TikZ figures
5
5
  Author-email: Thomas Saigre <tikzplotly@outlook.fr>
6
6
  License: The MIT License (MIT)
@@ -44,6 +44,7 @@ License-File: LICENSE
44
44
  Requires-Dist: numpy
45
45
  Requires-Dist: plotly
46
46
  Requires-Dist: pillow
47
+ Requires-Dist: kaleido
47
48
  Provides-Extra: dev
48
49
  Requires-Dist: pandas; extra == "dev"
49
50
  Dynamic: license-file
@@ -16,7 +16,7 @@ src/tikzplotly/_bar.py
16
16
  src/tikzplotly/_color.py
17
17
  src/tikzplotly/_dash.py
18
18
  src/tikzplotly/_data.py
19
- src/tikzplotly/_dataContainer.py
19
+ src/tikzplotly/_data_container.py
20
20
  src/tikzplotly/_heatmap.py
21
21
  src/tikzplotly/_histogram.py
22
22
  src/tikzplotly/_marker.py
@@ -25,6 +25,7 @@ src/tikzplotly/_save.py
25
25
  src/tikzplotly/_scatter.py
26
26
  src/tikzplotly/_scatter3d.py
27
27
  src/tikzplotly/_tex.py
28
+ src/tikzplotly/_trace_utils.py
28
29
  src/tikzplotly/_utils.py
29
30
  src/tikzplotly.egg-info/PKG-INFO
30
31
  src/tikzplotly.egg-info/SOURCES.txt
@@ -32,6 +33,7 @@ src/tikzplotly.egg-info/dependency_links.txt
32
33
  src/tikzplotly.egg-info/requires.txt
33
34
  src/tikzplotly.egg-info/top_level.txt
34
35
  tests/test_bars.py
36
+ tests/test_colors.py
35
37
  tests/test_heatmap.py
36
38
  tests/test_histograms.py
37
39
  tests/test_markers.py
@@ -1,6 +1,7 @@
1
1
  numpy
2
2
  plotly
3
3
  pillow
4
+ kaleido
4
5
 
5
6
  [dev]
6
7
  pandas
@@ -0,0 +1,53 @@
1
+ import os
2
+ import pathlib
3
+ from contextlib import nullcontext
4
+
5
+ import numpy
6
+ import plotly.graph_objects as go
7
+ import plotly.express as px
8
+ import pytest
9
+
10
+ from .helpers import assert_equality
11
+
12
+
13
+ this_dir = pathlib.Path(__file__).resolve().parent
14
+ test_name = "test_colors"
15
+
16
+
17
+ def plot_color(color_scheme):
18
+ fig = go.Figure()
19
+ fig.add_trace(go.Scatter(x=[0, 1, 2, 3, 4], y=[0, 1, 4, 9, 16], marker_color=color_scheme))
20
+ return fig
21
+
22
+ def plot_transparent_background():
23
+ fig = px.scatter(x=[0, 1, 2, 3, 4], y=[0, 1, 4, 9, 16])
24
+ fig.update_layout(plot_bgcolor='rgba(255, 182, 193, .5)')
25
+
26
+ return fig
27
+
28
+
29
+ @pytest.mark.parametrize(
30
+ "color, warning_match",
31
+ [
32
+ pytest.param(None, None, id="none"),
33
+ pytest.param(numpy.array([1, 2, 3]), "Color from data is not supported yet", id="numpy-array"), # ("blue", "HTML", "0000ff", 1)
34
+ pytest.param(123, "Color 123 type 'int' is not supported yet", id="non-string"), # ("blue", "HTML", "0000ff", 1)
35
+ pytest.param("#ffb6c1", None, id="hex"),
36
+ pytest.param("rgba(255, 182, 193, .2)", None, id="rgba-with-opacity"),
37
+ pytest.param("rgba(255, 182, 193)", None, id="rgba-without-opacity"),
38
+ pytest.param("rgb(255, 182, 193)", None, id="rgb"),
39
+ pytest.param("red", None, id="named-color"),
40
+ pytest.param("LightBlue", None, id="colors-dict-hit"),
41
+ ],
42
+ )
43
+ def test_color(color, warning_match, request):
44
+ id = request.node.callspec.id
45
+ if warning_match is None:
46
+ context = nullcontext()
47
+ else:
48
+ context = pytest.warns(UserWarning, match=warning_match)
49
+ with context:
50
+ assert_equality(plot_color(color), os.path.join(this_dir, test_name, f"{test_name}_{id}_reference.tex"))
51
+
52
+ def test_transparent_background():
53
+ assert_equality(plot_transparent_background(), os.path.join(this_dir, test_name, test_name + "_transparent_background_reference.tex"))
@@ -1,6 +1,7 @@
1
1
  import plotly.express as px
2
2
  import plotly.graph_objects as go
3
3
  import numpy as np
4
+ import pytest
4
5
  import os
5
6
  from .helpers import assert_equality
6
7
  import pathlib
@@ -22,6 +23,7 @@ def plot_2():
22
23
  return fig
23
24
 
24
25
  def plot_3():
26
+ # A plot with no colorscale
25
27
  fig = go.Figure(data=go.Heatmap(
26
28
  z=[[1, None, 30, 50, 1], [20, 1, 60, 80, 30], [30, 60, 1, -10, 20]],
27
29
  x=['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
@@ -35,7 +37,7 @@ def plot_4():
35
37
  programmers = ['Alex','Nicole','Sara','Etienne','Chelsea','Jody','Marianne']
36
38
 
37
39
  base = datetime.datetime(2021, 7, 20, 19, 30, 0)
38
- dates = base - np.arange(180) * datetime.timedelta(days=1)
40
+ dates = [base - datetime.timedelta(days=int(i)) for i in np.arange(180)]
39
41
  np.random.seed(43)
40
42
  z = np.random.poisson(size=(len(programmers), len(dates)))
41
43
 
@@ -51,7 +53,7 @@ def plot_4():
51
53
 
52
54
  return fig
53
55
 
54
- def plot_5():
56
+ def plot_empty_trace():
55
57
  fig = px.imshow([[1, 20, 30],
56
58
  [20, 1, 60],
57
59
  [30, 60, 1]])
@@ -65,10 +67,12 @@ def test_2():
65
67
  assert_equality(plot_2(), os.path.join(this_dir, test_name, test_name + "_2_reference.tex"), img_name="/tmp/tikzplotly/fig2.png")
66
68
 
67
69
  def test_3():
68
- assert_equality(plot_3(), os.path.join(this_dir, test_name, test_name + "_3_reference.tex"), img_name="/tmp/tikzplotly/fig3.png")
70
+ with pytest.warns(UserWarning, match="No colorscale found, using default"):
71
+ assert_equality(plot_3(), os.path.join(this_dir, test_name, test_name + "_3_reference.tex"), img_name="/tmp/tikzplotly/fig3.png")
69
72
 
70
73
  def test_4():
71
74
  assert_equality(plot_4(), os.path.join(this_dir, test_name, test_name + "_4_reference.tex"), img_name="/tmp/tikzplotly/fig4.png")
72
75
 
73
- def test_5():
74
- assert_equality(plot_5(), os.path.join(this_dir, test_name, test_name + "_5_reference.tex"))
76
+ def test_empty_trace():
77
+ with pytest.warns(UserWarning, match="Adding empty trace."):
78
+ assert_equality(plot_empty_trace(), os.path.join(this_dir, test_name, test_name + "_5_reference.tex"))
@@ -2,6 +2,7 @@ import plotly.express as px
2
2
  import plotly.graph_objects as go
3
3
  import numpy as np
4
4
  import os
5
+ from contextlib import nullcontext
5
6
  from .helpers import assert_equality
6
7
  import pathlib
7
8
  import pytest
@@ -77,7 +78,12 @@ def test_3():
77
78
 
78
79
  @pytest.mark.parametrize("histnorm", ["percent", "probability", "density", "probability density"])
79
80
  def test_4(histnorm):
80
- assert_equality(plot_4(histnorm), os.path.join(this_dir, test_name, test_name + "_4_reference.tex"))
81
+ if histnorm in ["percent", "probability", "density"]:
82
+ context = pytest.warns(UserWarning, match=r"Sorry, I did not find an equivalent for histnorm='\w+' in TikZ*")
83
+ else:
84
+ context = nullcontext()
85
+ with context:
86
+ assert_equality(plot_4(histnorm), os.path.join(this_dir, test_name, test_name + "_4_reference.tex"))
81
87
 
82
88
  def test_5():
83
89
  assert_equality(plot_5(), os.path.join(this_dir, test_name, test_name + "_5_reference.tex"))
@@ -86,10 +92,11 @@ def test_6():
86
92
  assert_equality(plot_6(), os.path.join(this_dir, test_name, test_name + "_6_reference.tex"))
87
93
 
88
94
  def test_7():
89
- assert_equality(plot_7(), os.path.join(this_dir, test_name, test_name + "_7_reference.tex"))
95
+ with pytest.warns(UserWarning, match="To the best of our knowledge, other aggregate function than 'count' are not supported in pgfplots.*"):
96
+ assert_equality(plot_7(), os.path.join(this_dir, test_name, test_name + "_7_reference.tex"))
90
97
 
91
98
  def test_8():
92
99
  assert_equality(plot_8(), os.path.join(this_dir, test_name, test_name + "_8_reference.tex"))
93
100
 
94
101
  def test_9():
95
- assert_equality(plot_9(), os.path.join(this_dir, test_name, test_name + "_9_reference.tex"))
102
+ assert_equality(plot_9(), os.path.join(this_dir, test_name, test_name + "_9_reference.tex"))
@@ -3,6 +3,7 @@ import plotly.graph_objects as go
3
3
  import pytest
4
4
  import numpy as np
5
5
  import os
6
+ from contextlib import nullcontext
6
7
  from .helpers import assert_equality
7
8
  import pathlib
8
9
 
@@ -96,7 +97,12 @@ def plot_with_angle():
96
97
 
97
98
  @pytest.mark.parametrize("symbol", ["circle", 0, "0", "circle-dot"])
98
99
  def test_1(symbol):
99
- assert_equality(plot_1(symbol), os.path.join(this_dir, test_name, test_name + "_1_reference.tex"))
100
+ if symbol == "circle-dot":
101
+ context = pytest.warns(UserWarning, match=r"Dotted markers are not supported \(yet\), the symbol without dot will be used instead.")
102
+ else:
103
+ context = nullcontext()
104
+ with context:
105
+ assert_equality(plot_1(symbol), os.path.join(this_dir, test_name, test_name + "_1_reference.tex"))
100
106
 
101
107
  def test_2():
102
108
  assert_equality(plot_2(), os.path.join(this_dir, test_name, test_name + "_2_reference.tex"))
@@ -5,6 +5,7 @@ import os, pathlib
5
5
  import plotly.express as px
6
6
  import plotly.graph_objects as go
7
7
  import pandas as pd
8
+ import pytest
8
9
  from .helpers import assert_equality
9
10
 
10
11
  this_dir = pathlib.Path(__file__).resolve().parent
@@ -275,4 +276,5 @@ def test_polar_1():
275
276
  assert_equality(fig_polar_1(), os.path.join(this_dir, test_name, test_name + "_1_reference.tex"))
276
277
 
277
278
  def test_polar_matplotlib():
278
- assert_equality(fig_polar_matplotlib(), os.path.join(this_dir, "empty_plot.tex"))
279
+ with pytest.warns(UserWarning, match="Trace type barpolar is not supported yet"):
280
+ assert_equality(fig_polar_matplotlib(), os.path.join(this_dir, "empty_plot.tex"))
@@ -2,6 +2,7 @@ import plotly.express as px
2
2
  import plotly.graph_objects as go
3
3
  import numpy as np
4
4
  import os
5
+ from contextlib import nullcontext
5
6
  from .helpers import assert_equality
6
7
  import pathlib
7
8
  import pytest
@@ -220,9 +221,6 @@ def test_2():
220
221
  def test_tranparent_color():
221
222
  assert_equality(plot_transparent_color(), os.path.join(this_dir, test_name, test_name + "_transparent_color_reference.tex"))
222
223
 
223
- # def test_tranparent_color_rgba():
224
- # assert_equality(plot_transparent_color_rgba(), os.path.join(this_dir, test_name, test_name + "_transparent_color_rgba_reference.tex"))
225
-
226
224
  def test_3():
227
225
  assert_equality(plot_3(), os.path.join(this_dir, test_name, test_name + "_3_reference.tex"))
228
226
 
@@ -230,14 +228,18 @@ def test_4():
230
228
  assert_equality(plot_4(), os.path.join(this_dir, test_name, test_name + "_4_reference.tex"))
231
229
 
232
230
  def test_5():
233
- assert_equality(plot_5(), os.path.join(this_dir, test_name, test_name + "_5_reference.tex"))
231
+ with pytest.warns(UserWarning, match="Assuming this is a date*"):
232
+ assert_equality(plot_5(), os.path.join(this_dir, test_name, test_name + "_5_reference.tex"))
234
233
 
235
234
  @pytest.mark.parametrize("x, y", [(True, True), (True, False), (False, True)])
236
235
  def test_6(x, y):
237
- assert_equality(plot_6(x, y), os.path.join(this_dir, test_name, test_name + f"_6_{x}_{y}_reference.tex"))
236
+ context = pytest.warns(UserWarning, match="Adding empty trace.") if x and y else nullcontext()
237
+ with context:
238
+ assert_equality(plot_6(x, y), os.path.join(this_dir, test_name, test_name + f"_6_{x}_{y}_reference.tex"))
238
239
 
239
240
  def test_7():
240
- assert_equality(plot_7(), os.path.join(this_dir, test_name, test_name + "_7_reference.tex"))
241
+ with pytest.warns(UserWarning, match="Assuming data January is a month*"):
242
+ assert_equality(plot_7(), os.path.join(this_dir, test_name, test_name + "_7_reference.tex"))
241
243
 
242
244
  def test_8():
243
245
  assert_equality(plot_8(), os.path.join(this_dir, test_name, test_name + "_8_reference.tex"))
@@ -2,6 +2,7 @@
2
2
  Test of 3D scatter plots https://plotly.com/python/3d-scatter-plots/
3
3
  """
4
4
  import os, pathlib
5
+ from contextlib import nullcontext
5
6
  import plotly.express as px
6
7
  import plotly.graph_objects as go
7
8
  import numpy as np
@@ -88,7 +89,13 @@ def test_scatter_3d_1():
88
89
 
89
90
  @pytest.mark.parametrize("mode", ["markers", "markers+lines", "lines"])
90
91
  def test_scatter_3d_2(mode):
91
- assert_equality(plot_scatter_3d_2(mode), os.path.join(this_dir, test_name, test_name + f"_2_{mode}_reference.tex"))
92
+ if "markers" in mode:
93
+ context = pytest.warns(UserWarning, match="Color from data is not supported yet*")
94
+ # context = nullcontext()
95
+ else:
96
+ context = nullcontext()
97
+ with context:
98
+ assert_equality(plot_scatter_3d_2(mode), os.path.join(this_dir, test_name, test_name + f"_2_{mode}_reference.tex"))
92
99
 
93
100
  def test_scatter_3d_3():
94
101
  assert_equality(plot_scatter_3d_3(), os.path.join(this_dir, test_name, test_name + "_3_reference.tex"))
@@ -97,4 +104,5 @@ def test_scatter_3d_view():
97
104
  assert_equality(plot_scatter_3d_view(), os.path.join(this_dir, test_name, test_name + "_view_reference.tex"))
98
105
 
99
106
  def test_scatter_3d_empty():
100
- assert_equality(plot_scatter_3d_empty(), os.path.join(this_dir, test_name, test_name + "_empty_reference.tex"))
107
+ with pytest.warns(UserWarning, match="Adding empty 3D trace."):
108
+ assert_equality(plot_scatter_3d_empty(), os.path.join(this_dir, test_name, test_name + "_empty_reference.tex"))
@@ -2,8 +2,9 @@
2
2
  In this file are present the test of some very specific usage case, that should occur very rarely.
3
3
  """
4
4
  import os, pathlib
5
- import plotly.express as px
5
+ import pytest
6
6
  import plotly.graph_objects as go
7
+ import plotly.express as px
7
8
  from .helpers import assert_equality
8
9
 
9
10
  this_dir = pathlib.Path(__file__).resolve().parent
@@ -29,24 +30,27 @@ def plot_sanitized_text():
29
30
 
30
31
  return fig
31
32
 
32
- def plot_transparent_background():
33
- # TODO: move it into test_color (when created)
34
- fig = px.scatter(x=[0, 1, 2, 3, 4], y=[0, 1, 4, 9, 16])
35
- fig.update_layout(plot_bgcolor='rgba(255, 182, 193, .5)')
36
33
 
37
- return fig
38
34
 
39
35
  def plot_empty_figure():
40
36
  fig = go.Figure()
41
- fig.show()
42
37
  return fig
43
38
 
39
+ def plot_empty_histogram():
40
+ # Normally user shouldn't create this kind on figure, but we never know !
41
+ fig = px.histogram(x=[1])
42
+ fig.data[0].x = None
43
+ return fig
44
44
 
45
- def test_sanitized_text():
46
- assert_equality(plot_sanitized_text(), os.path.join(this_dir, test_name, test_name + "_sanitized_text_reference.tex"))
47
45
 
48
- def test_transparent_background():
49
- assert_equality(plot_transparent_background(), os.path.join(this_dir, test_name, test_name + "_transparent_background_reference.tex"))
46
+ def test_sanitized_text():
47
+ with pytest.warns(UserWarning, match="Character .+ has been replaced by \"x[0-9a-f]+\" in output file"):
48
+ assert_equality(plot_sanitized_text(), os.path.join(this_dir, test_name, test_name + "_sanitized_text_reference.tex"))
50
49
 
51
50
  def test_empty_figure():
52
- assert_equality(plot_empty_figure(), os.path.join(this_dir, "empty_plot.tex"))
51
+ with pytest.warns(UserWarning, match="No data in figure."):
52
+ assert_equality(plot_empty_figure(), os.path.join(this_dir, "empty_plot.tex"))
53
+
54
+ def test_empty_histogram():
55
+ with pytest.warns(UserWarning, match="Empty histogram.*"):
56
+ assert_equality(plot_empty_histogram(), os.path.join(this_dir, test_name, test_name + "_empty_histogram_reference.tex"))
File without changes
File without changes
File without changes