tikzplotly 0.1.7__tar.gz → 0.2.0__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 (36) hide show
  1. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/PKG-INFO +11 -6
  2. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/README.md +7 -4
  3. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/pyproject.toml +4 -2
  4. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tests/test_heatmaps.py +9 -9
  5. tikzplotly-0.2.0/src/tests/test_histograms.py +284 -0
  6. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tests/test_line_charts.py +26 -1
  7. tikzplotly-0.2.0/src/tikzplotly/__about__.py +1 -0
  8. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tikzplotly/_axis.py +86 -36
  9. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tikzplotly/_heatmap.py +1 -1
  10. tikzplotly-0.2.0/src/tikzplotly/_histogram.py +132 -0
  11. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tikzplotly/_save.py +9 -7
  12. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tikzplotly/_tex.py +30 -1
  13. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tikzplotly/_utils.py +49 -4
  14. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tikzplotly.egg-info/PKG-INFO +11 -6
  15. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tikzplotly.egg-info/SOURCES.txt +3 -0
  16. tikzplotly-0.2.0/tests/test_histograms.py +95 -0
  17. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/tests/test_scatter.py +72 -3
  18. tikzplotly-0.2.0/tests/test_tikzplotly.py +39 -0
  19. tikzplotly-0.1.7/src/tikzplotly/__about__.py +0 -1
  20. tikzplotly-0.1.7/tests/test_tikzplotly.py +0 -6
  21. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/LICENSE +0 -0
  22. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/setup.cfg +0 -0
  23. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tests/test_markers.py +0 -0
  24. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tikzplotly/__init__.py +0 -0
  25. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tikzplotly/_annotations.py +0 -0
  26. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tikzplotly/_color.py +0 -0
  27. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tikzplotly/_dash.py +0 -0
  28. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tikzplotly/_data.py +0 -0
  29. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tikzplotly/_dataContainer.py +0 -0
  30. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tikzplotly/_marker.py +0 -0
  31. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tikzplotly/_scatter.py +0 -0
  32. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tikzplotly.egg-info/dependency_links.txt +0 -0
  33. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tikzplotly.egg-info/requires.txt +0 -0
  34. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/src/tikzplotly.egg-info/top_level.txt +0 -0
  35. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/tests/test_heatmap.py +0 -0
  36. {tikzplotly-0.1.7 → tikzplotly-0.2.0}/tests/test_markers.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: tikzplotly
3
- Version: 0.1.7
3
+ Version: 0.2.0
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)
@@ -30,9 +30,11 @@ Keywords: plotly,latex,tikz,figure,conversion,graphics
30
30
  Classifier: Development Status :: 5 - Production/Stable
31
31
  Classifier: Programming Language :: Python
32
32
  Classifier: Programming Language :: Python :: 3
33
- Classifier: Programming Language :: Python :: 3.8
34
33
  Classifier: Programming Language :: Python :: 3.9
35
34
  Classifier: Programming Language :: Python :: 3.10
35
+ Classifier: Programming Language :: Python :: 3.11
36
+ Classifier: Programming Language :: Python :: 3.12
37
+ Classifier: Programming Language :: Python :: 3.13
36
38
  Classifier: License :: OSI Approved :: MIT License
37
39
  Classifier: Operating System :: OS Independent
38
40
  Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
@@ -45,11 +47,15 @@ Requires-Dist: pillow
45
47
  Provides-Extra: dev
46
48
  Requires-Dist: pandas; extra == "dev"
47
49
 
48
- ![logo-tikzplotly](https://raw.githubusercontent.com/thomas-saigre/tikzplotly/main/doc/img/logo.svg "Tikzplotly")
50
+ ![logo-tikzplotly](https://raw.githubusercontent.com/thomas-saigre/tikzplotly/main/docs/assets/logo.svg "Tikzplotly")
51
+
49
52
 
50
53
  [![PyPi Version](https://img.shields.io/pypi/v/tikzplotly.svg?style=flat-square)](https://pypi.org/project/tikzplotly)
54
+ [![PyPi Downloads](https://img.shields.io/pypi/dm/tikzplotly.svg?style=flat-square)](https://pypi.org/project/tikzplotly)
55
+ [![PyPi License](https://img.shields.io/pypi/l/tikzplotly.svg?style=flat-square)](https://pypi.org/project/tikzplotly)
51
56
  [![PyPI pyversions](https://img.shields.io/pypi/pyversions/tikzplotly.svg?style=flat-square)](https://pypi.org/pypi/tikzplotly/)
52
57
 
58
+ [![GitHub release](https://img.shields.io/github/release/thomas-saigre/tikzplotly.svg?style=flat-square)](https://github.com/thomas-saigre/tikzplotly/releases)
53
59
  [![Issue](https://img.shields.io/github/issues-raw/thomas-saigre/tikzplotly?style=flat-square)](https://github.com/thomas-saigre/tikzplotly/issues)
54
60
  [![codecov](https://img.shields.io/codecov/c/github/thomas-saigre/tikzplotly.svg?style=flat-square)](https://codecov.io/gh/thomas-saigre/tikzplotly)
55
61
  ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/thomas-saigre/tikzplotly/ci.yml?style=flat-square)
@@ -142,8 +148,7 @@ To correctly compile the document, you will need to add the following packages t
142
148
 
143
149
  * This module is in development and new features are added bit by bit, when needed. If you have a feature request, please open an issue with the plotly figure you want to convert and the desired output.
144
150
  You can also submit a pull request with the desired feature !
145
- * Some feature can result in different output between the plotly figure and the tikz figure, for instance the size of markers.
146
- * More details in [doc/NB.md](doc/NB.md).
151
+ * Some feature can result in different output between the plotly figure and the tikz figure, for instance the size of markers, more details can be found in the documentation.
147
152
 
148
153
 
149
154
  ## References
@@ -1,8 +1,12 @@
1
- ![logo-tikzplotly](https://raw.githubusercontent.com/thomas-saigre/tikzplotly/main/doc/img/logo.svg "Tikzplotly")
1
+ ![logo-tikzplotly](https://raw.githubusercontent.com/thomas-saigre/tikzplotly/main/docs/assets/logo.svg "Tikzplotly")
2
+
2
3
 
3
4
  [![PyPi Version](https://img.shields.io/pypi/v/tikzplotly.svg?style=flat-square)](https://pypi.org/project/tikzplotly)
5
+ [![PyPi Downloads](https://img.shields.io/pypi/dm/tikzplotly.svg?style=flat-square)](https://pypi.org/project/tikzplotly)
6
+ [![PyPi License](https://img.shields.io/pypi/l/tikzplotly.svg?style=flat-square)](https://pypi.org/project/tikzplotly)
4
7
  [![PyPI pyversions](https://img.shields.io/pypi/pyversions/tikzplotly.svg?style=flat-square)](https://pypi.org/pypi/tikzplotly/)
5
8
 
9
+ [![GitHub release](https://img.shields.io/github/release/thomas-saigre/tikzplotly.svg?style=flat-square)](https://github.com/thomas-saigre/tikzplotly/releases)
6
10
  [![Issue](https://img.shields.io/github/issues-raw/thomas-saigre/tikzplotly?style=flat-square)](https://github.com/thomas-saigre/tikzplotly/issues)
7
11
  [![codecov](https://img.shields.io/codecov/c/github/thomas-saigre/tikzplotly.svg?style=flat-square)](https://codecov.io/gh/thomas-saigre/tikzplotly)
8
12
  ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/thomas-saigre/tikzplotly/ci.yml?style=flat-square)
@@ -95,8 +99,7 @@ To correctly compile the document, you will need to add the following packages t
95
99
 
96
100
  * This module is in development and new features are added bit by bit, when needed. If you have a feature request, please open an issue with the plotly figure you want to convert and the desired output.
97
101
  You can also submit a pull request with the desired feature !
98
- * Some feature can result in different output between the plotly figure and the tikz figure, for instance the size of markers.
99
- * More details in [doc/NB.md](doc/NB.md).
102
+ * Some feature can result in different output between the plotly figure and the tikz figure, for instance the size of markers, more details can be found in the documentation.
100
103
 
101
104
 
102
105
  ## References
@@ -109,4 +112,4 @@ You can also submit a pull request with the desired feature !
109
112
 
110
113
  ## License
111
114
 
112
- This project is licensed under the MIT [License](LICENSE).
115
+ This project is licensed under the MIT [License](LICENSE).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tikzplotly"
7
- version = "0.1.7"
7
+ version = "0.2.0"
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"}]
@@ -13,9 +13,11 @@ classifiers = [
13
13
  "Development Status :: 5 - Production/Stable",
14
14
  "Programming Language :: Python",
15
15
  "Programming Language :: Python :: 3",
16
- "Programming Language :: Python :: 3.8",
17
16
  "Programming Language :: Python :: 3.9",
18
17
  "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
19
21
  "License :: OSI Approved :: MIT License",
20
22
  "Operating System :: OS Independent",
21
23
  "Topic :: Multimedia :: Graphics :: Graphics Conversion",
@@ -166,16 +166,16 @@ if __name__ == "__main__":
166
166
  file_directory = os.path.dirname(os.path.abspath(__file__))
167
167
 
168
168
  functions = [
169
- # ("1", fig1),
170
- # ("2", fig2),
171
- # # ("3", fig3), # texttemplate not supported
172
- # ("4", fig4),
173
- # ("5", fig5),
174
- # ("6", fig6),
169
+ ("1", fig1),
170
+ ("2", fig2),
171
+ # ("3", fig3), # texttemplate not supported
172
+ ("4", fig4),
173
+ ("5", fig5),
174
+ ("6", fig6),
175
175
  ("7", fig7),
176
- # # ("8", fig8), # Not supported
177
- # ("9", fig9),
178
- # ("11", fig11)
176
+ # ("8", fig8), # Not supported
177
+ ("9", fig9),
178
+ ("11", fig11)
179
179
  ]
180
180
 
181
181
  main_tex_content = tex_create_document(options="twocolumn", compatibility="newest")
@@ -0,0 +1,284 @@
1
+ # From https://plotly.com/python/histograms/
2
+ import plotly
3
+ import plotly.express as px
4
+ import plotly.graph_objects as go
5
+ from plotly.subplots import make_subplots
6
+
7
+ import numpy as np
8
+ import tikzplotly
9
+ import os
10
+ from warnings import warn
11
+ from tikzplotly._tex import tex_create_document, tex_begin_environment, tex_end_environment, tex_end_all_environment
12
+
13
+ def fig1():
14
+ df = px.data.tips()
15
+ fig = px.histogram(df, x="total_bill")
16
+ # fig.show()
17
+ return fig, "Histogram of total bill"
18
+
19
+ def fig2():
20
+ df = px.data.tips()
21
+ # Here we use a column with categorical data
22
+ fig = px.histogram(df, x="day")
23
+ # fig.show()
24
+ return fig, "Histogram of days"
25
+
26
+ def fig3():
27
+ df = px.data.tips()
28
+ fig = px.histogram(df, x="total_bill", nbins=20)
29
+ # fig.show()
30
+ return fig, "Choose the number of bins"
31
+
32
+ def fig4():
33
+ warn("NB: This example is quite not working because of the high number of different dates present in the data set")
34
+ df = px.data.stocks()
35
+ fig = px.histogram(df, x="date")
36
+ fig.update_layout(bargap=0.2)
37
+ # fig.show()
38
+ return fig, "Histogram on date data"
39
+
40
+ def fig5():
41
+ df = px.data.tips()
42
+ fig = px.histogram(df, x="day", category_orders=dict(day=["Thur", "Fri", "Sat", "Sun"]))
43
+ # fig.show()
44
+ return fig, "Histograms on Categorical Data"
45
+
46
+ def fig6():
47
+ df = px.data.tips()
48
+ fig = px.histogram(df, x="total_bill", histnorm='probability density')
49
+ # fig.show()
50
+ return fig, "Type of normalization"
51
+
52
+ def fig7():
53
+ df = px.data.tips()
54
+ fig = px.histogram(df, x="total_bill",
55
+ title='Histogram of bills',
56
+ labels={'total_bill':'total bill'}, # can specify one label per df column
57
+ opacity=0.8,
58
+ log_y=True, # represent bars with log scale
59
+ color_discrete_sequence=['indianred'] # color of histogram bars
60
+ )
61
+ # fig.show()
62
+ return fig, "Aspect of the histogram plot"
63
+
64
+ def fig8():
65
+ df = px.data.tips()
66
+ fig = px.histogram(df, x="total_bill", color="sex")
67
+ # fig.show()
68
+ return fig, "Several histograms for different values of one column"
69
+
70
+ def fig9():
71
+ df = px.data.tips()
72
+ fig = px.histogram(df, x="total_bill", color="sex", marginal="rug", # can be `box`, `violin`
73
+ hover_data=df.columns)
74
+ return fig, "Histograms with pattern shapes"
75
+
76
+ def fig10():
77
+ df = px.data.tips()
78
+ fig = px.histogram(df, x="total_bill", y="tip", histfunc="avg", nbins=8, text_auto=True)
79
+ return fig, "Adding text labels"
80
+
81
+ def fig11():
82
+ np.random.seed(1)
83
+ x = np.random.randn(500)
84
+ fig = go.Figure(data=[go.Histogram(x=x)])
85
+ return fig, "Basic Histogram"
86
+
87
+ def fig12():
88
+ x = np.random.randn(500)
89
+ fig = go.Figure(data=[go.Histogram(x=x, histnorm='probability')])
90
+ return fig, "Normalized Histogram"
91
+
92
+ def fig13():
93
+ y = np.random.randn(500)
94
+ fig = go.Figure(data=[go.Histogram(y=y)])
95
+ return fig, "Horizontal Histogram"
96
+
97
+ def fig14():
98
+
99
+ x0 = np.random.randn(500)
100
+ x1 = np.random.randn(500) + 1
101
+
102
+ fig = go.Figure()
103
+ fig.add_trace(go.Histogram(x=x0))
104
+ fig.add_trace(go.Histogram(x=x1))
105
+
106
+ # Overlay both histograms
107
+ fig.update_layout(barmode='overlay')
108
+ # Reduce opacity to see both histograms
109
+ fig.update_traces(opacity=0.75)
110
+ return fig, "Overlay Histograms"
111
+
112
+ def fig15():
113
+ x0 = np.random.randn(500)
114
+ x1 = np.random.randn(500)
115
+
116
+ fig = go.Figure()
117
+ fig.add_trace(go.Histogram(x=x0))
118
+ fig.add_trace(go.Histogram(x=x1))
119
+
120
+ # The two histograms are drawn on top of another
121
+ fig.update_layout(barmode='stack')
122
+ return fig, "Stacked Histograms"
123
+
124
+ def fig16():
125
+ x0 = np.random.randn(500)
126
+ x1 = np.random.randn(500) + 1
127
+
128
+ fig = go.Figure()
129
+ fig.add_trace(go.Histogram(
130
+ x=x0,
131
+ histnorm='percent',
132
+ name='control', # name used in legend and hover labels
133
+ xbins=dict( # bins used for histogram
134
+ start=-4.0,
135
+ end=3.0,
136
+ size=0.5
137
+ ),
138
+ marker_color='#EB89B5',
139
+ opacity=0.75
140
+ ))
141
+ fig.add_trace(go.Histogram(
142
+ x=x1,
143
+ histnorm='percent',
144
+ name='experimental',
145
+ xbins=dict(
146
+ start=-3.0,
147
+ end=4,
148
+ size=0.5
149
+ ),
150
+ marker_color='#330C73',
151
+ opacity=0.75
152
+ ))
153
+
154
+ fig.update_layout(
155
+ title_text='Sampled Results', # title of plot
156
+ xaxis_title_text='Value', # xaxis label
157
+ yaxis_title_text='Count', # yaxis label
158
+ bargap=0.2, # gap between bars of adjacent location coordinates
159
+ bargroupgap=0.1 # gap between bars of the same location coordinates
160
+ )
161
+ return fig, "Styled Histogram"
162
+
163
+ def fig17():
164
+ numbers = ["5", "10", "3", "10", "5", "8", "5", "5"]
165
+ fig = go.Figure()
166
+ fig.add_trace(go.Histogram(x=numbers, name="count", texttemplate="%{x}", textfont_size=20))
167
+ return fig, "Histogram Bar Text"
168
+
169
+ def fig18():
170
+ x = np.random.randn(500)
171
+ fig = go.Figure(data=[go.Histogram(x=x, cumulative_enabled=True)])
172
+ return fig, "Cumulative Histogram"
173
+
174
+ def fig19():
175
+ x = ["Apples","Apples","Apples","Oranges", "Bananas"]
176
+ y = ["5","10","3","10","5"]
177
+
178
+ fig = go.Figure()
179
+ fig.add_trace(go.Histogram(histfunc="count", y=y, x=x, name="count"))
180
+ fig.add_trace(go.Histogram(histfunc="sum", y=y, x=x, name="sum"))
181
+ return fig, "Specify Aggregation Function"
182
+
183
+ def fig20():
184
+ x = ['1970-01-01', '1970-01-01', '1970-02-01', '1970-04-01', '1970-01-02',
185
+ '1972-01-31', '1970-02-13', '1971-04-19']
186
+
187
+ fig = make_subplots(rows=3, cols=2)
188
+
189
+ trace0 = go.Histogram(x=x, nbinsx=4)
190
+ trace1 = go.Histogram(x=x, nbinsx = 8)
191
+ trace2 = go.Histogram(x=x, nbinsx=10)
192
+ trace3 = go.Histogram(x=x,
193
+ xbins=dict(
194
+ start='1969-11-15',
195
+ end='1972-03-31',
196
+ size='M18'), # M18 stands for 18 months
197
+ autobinx=False
198
+ )
199
+ trace4 = go.Histogram(x=x,
200
+ xbins=dict(
201
+ start='1969-11-15',
202
+ end='1972-03-31',
203
+ size='M4'), # 4 months bin size
204
+ autobinx=False
205
+ )
206
+ trace5 = go.Histogram(x=x,
207
+ xbins=dict(
208
+ start='1969-11-15',
209
+ end='1972-03-31',
210
+ size= 'M2'), # 2 months
211
+ autobinx = False
212
+ )
213
+
214
+ fig.append_trace(trace0, 1, 1)
215
+ fig.append_trace(trace1, 1, 2)
216
+ fig.append_trace(trace2, 2, 1)
217
+ fig.append_trace(trace3, 2, 2)
218
+ fig.append_trace(trace4, 3, 1)
219
+ fig.append_trace(trace5, 3, 2)
220
+
221
+ return fig, "Custom Binning"
222
+
223
+ def fig21():
224
+ df = px.data.tips()
225
+ fig = px.histogram(df, x="day").update_xaxes(categoryorder='total ascending')
226
+ return fig, "Sort Histogram by Category Order"
227
+
228
+
229
+ if __name__ == "__main__":
230
+
231
+ print("Tikzploty : ", tikzplotly.__version__)
232
+ print("Plotly : ", plotly.__version__)
233
+ print("Test histograms")
234
+
235
+
236
+ file_directory = os.path.dirname(os.path.abspath(__file__))
237
+
238
+ functions = [
239
+ ("1", fig1),
240
+ ("2", fig2),
241
+ ("3", fig3),
242
+ # ("4", fig4),
243
+ ("5", fig5),
244
+ ("6", fig6),
245
+ ("7", fig7),
246
+ ("8", fig8),
247
+ # ("9", fig9), # Trace type box is not yet supported
248
+ # ("10", fig10), # Text template is not supported yet.
249
+ ("11", fig11),
250
+ ("12", fig12),
251
+ ("13", fig13),
252
+ ("14", fig14),
253
+ ("15", fig15),
254
+ # ("16", fig16), # Layout of this example not supported yet
255
+ ("17", fig17), # Text template is not supported yet.
256
+ ("18", fig18),
257
+ ("19", fig19),
258
+ # ("20", fig20), # Subplots not supported yet
259
+ ("21", fig21) # Category order not supported yet
260
+ ]
261
+
262
+ main_tex_content = tex_create_document(options="twocolumn", compatibility="newest")
263
+ main_tex_content += "\\usepackage[left=1cm, right=1cm, top=1cm, bottom=1cm]{geometry}\n"
264
+ main_tex_content += "\n"
265
+ stack_env = []
266
+ main_tex_content += tex_begin_environment("document", stack_env) + '\n'
267
+
268
+ for i, f in functions:
269
+ print(f"Figure {i}")
270
+ fig, title = f()
271
+ data = fig.data
272
+ save_path = os.path.join(file_directory, "outputs", "test_histograms", "fig{}.tex".format(i))
273
+ tikzplotly.save(save_path, fig)
274
+ main_tex_content += tex_begin_environment("figure", stack_env)
275
+ main_tex_content += " \\input{fig" + str(i) + ".tex}\n"
276
+ main_tex_content += " \\caption{" + title + "}\n"
277
+ main_tex_content += tex_end_environment(stack_env) + '\n'
278
+
279
+ main_tex_content += "\n" + tex_end_all_environment(stack_env)
280
+
281
+ main_tex_path = os.path.join(file_directory, "outputs", "test_histograms", "main.tex")
282
+ print("Save main tex file : ", main_tex_path)
283
+ with open(main_tex_path, "w") as f:
284
+ f.write(main_tex_content)
@@ -371,6 +371,28 @@ def fig15():
371
371
  # fig.show()
372
372
  return fig, "Filled Lines"
373
373
 
374
+ # https://plotly.com/python/log-plot/
375
+
376
+ def fig16():
377
+ df = px.data.gapminder().query("year == 2007")
378
+ fig = px.scatter(df, x="gdpPercap", y="lifeExp", hover_name="country", log_x=True)
379
+ return fig, "Log Scale"
380
+
381
+ def fig17():
382
+ df = px.data.gapminder().query("year == 2007")
383
+
384
+ fig = px.scatter(df, x="gdpPercap", y="lifeExp", hover_name="country",
385
+ log_x=True, range_x=[1,100000], range_y=[0,100])
386
+ return fig, "Log scale with range"
387
+
388
+ def fig18():
389
+ df = px.data.gapminder().query("year == 2007")
390
+
391
+ fig = px.scatter(df, x="gdpPercap", y="lifeExp", hover_name="country",
392
+ log_x=True, range_x=[1,100000], range_y=[0,100])
393
+
394
+ fig.update_xaxes(minor=dict(ticks="inside", ticklen=6, showgrid=True))
395
+ return fig, "Log scale with range and minor ticks"
374
396
 
375
397
  if __name__ == "__main__":
376
398
 
@@ -394,7 +416,10 @@ if __name__ == "__main__":
394
416
  ("11", fig11),
395
417
  # ("12", fig12),
396
418
  ("14", fig14),
397
- ("15", fig15)
419
+ ("15", fig15),
420
+ ("16", fig16),
421
+ ("17", fig17),
422
+ ("18", fig18),
398
423
  ]
399
424
 
400
425
  main_tex_content = tex_create_document(options="twocolumn", compatibility="newest")
@@ -0,0 +1 @@
1
+ __version__ = "0.2.0"
@@ -1,6 +1,8 @@
1
1
  from ._color import convert_color
2
2
  from ._tex import tex_begin_environment
3
- from ._utils import sanitize_TeX_text, option_dict_to_str
3
+ from ._utils import sanitize_TeX_text, option_dict_to_str, get_ticks_str
4
+ from warnings import warn
5
+
4
6
  class Axis():
5
7
 
6
8
  def __init__(self, layout, colors_set, axis_options=None):
@@ -15,9 +17,8 @@ class Axis():
15
17
  axis_options
16
18
  options given to the axis environment, by default None. Can be a dict ({option: value}) or a string ("option1=value1, option2=value2").
17
19
  """
18
- self.x_label = layout.xaxis.title.text
19
- self.y_label = layout.yaxis.title.text
20
- self.title = layout.title.text
20
+ self.layout = layout
21
+
21
22
  self.options = {}
22
23
  if isinstance(axis_options, dict):
23
24
  self.options = axis_options
@@ -31,41 +32,14 @@ class Axis():
31
32
  self.options[option] = None
32
33
  self.environment = "axis"
33
34
 
34
- if layout.xaxis.visible is False:
35
- self.x_label = None
36
- self.add_option("hide x axis", None)
37
- if layout.yaxis.visible is False:
38
- self.y_label = None
39
- self.add_option("hide y axis", None)
35
+ self.xticks = None
36
+
37
+ self.treat_axis_layout()
38
+ self.treat_background_layout(colors_set)
39
+ self.treat_bar_layout()
40
40
 
41
- # Handle log axes
42
- if layout.xaxis.type == "log":
43
- self.add_option("xmode", "log")
44
- if layout.yaxis.type == "log":
45
- self.add_option("ymode", "log")
46
41
 
47
- # Handle range
48
- # In log mode, the range is the exponent of the range : https://plotly.com/python/reference/layout/xaxis/#layout-xaxis-range
49
- # For more information, refer to documentation https://plotly.com/python/reference/layout/xaxis/#layout-xaxis-autorange
50
- if layout.xaxis.autorange is False or layout.xaxis.range is not None:
51
- self.add_option("xmin", layout.xaxis.range[0] if layout.xaxis.type != "log" else 10**layout.xaxis.range[0])
52
- self.add_option("xmax", layout.xaxis.range[1] if layout.xaxis.type != "log" else 10**layout.xaxis.range[1])
53
- if layout.yaxis.autorange is False or layout.yaxis.range is not None:
54
- self.add_option("ymin", layout.yaxis.range[0] if layout.yaxis.type != "log" else 10**layout.yaxis.range[0])
55
- self.add_option("ymax", layout.yaxis.range[1] if layout.yaxis.type != "log" else 10**layout.yaxis.range[1])
56
- if layout.xaxis.autorange == "reversed":
57
- self.add_option("x dir", "reverse")
58
- if layout.yaxis.autorange == "reversed":
59
- self.add_option("y dir", "reverse")
60
42
 
61
- if layout.plot_bgcolor is not None:
62
- bg_color = convert_color(layout.plot_bgcolor)
63
- colors_set.add(bg_color[:3])
64
- opacity = bg_color[3]
65
- if opacity < 1:
66
- self.add_option("axis background/.style", f"{{fill={bg_color[0]}, opacity={opacity}}}")
67
- else:
68
- self.add_option("axis background/.style", f"{{fill={bg_color[0]}}}")
69
43
 
70
44
  def set_x_label(self, x_label):
71
45
  """Set the x label.
@@ -125,3 +99,79 @@ class Axis():
125
99
  self.options["ylabel"] = sanitize_TeX_text(self.y_label)
126
100
  options_str = option_dict_to_str(self.options, sep="\n")
127
101
  return options_str
102
+
103
+
104
+ def treat_axis_layout(self):
105
+ """Treat the layout of the axis."""
106
+
107
+ self.set_x_label(self.layout.xaxis.title.text)
108
+ self.set_y_label(self.layout.yaxis.title.text)
109
+ self.title = self.layout.title.text
110
+
111
+ if self.layout.xaxis.visible is False:
112
+ self.x_label = None
113
+ self.add_option("hide x axis", None)
114
+ if self.layout.yaxis.visible is False:
115
+ self.y_label = None
116
+ self.add_option("hide y axis", None)
117
+
118
+ # Handle log axes
119
+ if self.layout.xaxis.type == "log":
120
+ self.add_option("xmode", "log")
121
+ if self.layout.yaxis.type == "log":
122
+ self.add_option("ymode", "log")
123
+
124
+ # Handle range
125
+ # In log mode, the range is the exponent of the range : https://plotly.com/python/reference/layout/xaxis/#layout-xaxis-range
126
+ # For more information, refer to documentation https://plotly.com/python/reference/layout/xaxis/#layout-xaxis-autorange
127
+ if self.layout.xaxis.autorange is False or self.layout.xaxis.range is not None:
128
+ self.add_option("xmin", self.layout.xaxis.range[0] if self.layout.xaxis.type != "log" else 10**self.layout.xaxis.range[0])
129
+ self.add_option("xmax", self.layout.xaxis.range[1] if self.layout.xaxis.type != "log" else 10**self.layout.xaxis.range[1])
130
+ if self.layout.yaxis.autorange is False or self.layout.yaxis.range is not None:
131
+ self.add_option("ymin", self.layout.yaxis.range[0] if self.layout.yaxis.type != "log" else 10**self.layout.yaxis.range[0])
132
+ self.add_option("ymax", self.layout.yaxis.range[1] if self.layout.yaxis.type != "log" else 10**self.layout.yaxis.range[1])
133
+ if self.layout.xaxis.autorange == "reversed":
134
+ self.add_option("x dir", "reverse")
135
+ if self.layout.yaxis.autorange == "reversed":
136
+ self.add_option("y dir", "reverse")
137
+
138
+ if self.layout.xaxis.showline == False:
139
+ self.add_option("axis x line", "none")
140
+ if self.layout.yaxis.showline == False:
141
+ self.add_option("axis y line", "none")
142
+ if self.layout.xaxis.categoryorder == "array":
143
+ self.xticks = self.layout.xaxis.categoryarray
144
+ ticks, ticklabels = get_ticks_str(self.layout.xaxis.categoryarray)
145
+ self.add_option("xtick", ticks)
146
+ self.add_option("xticklabels", ticklabels)
147
+
148
+ if self.layout.xaxis.categoryorder != "trace" or self.layout.yaxis.categoryorder != "trace" or self.layout.xaxis.categoryorder != "total descending":
149
+ warn("The categoryorder option is not supported (yet 🤞) for the axis environment.")
150
+
151
+ if self.layout.xaxis.showgrid:
152
+ self.add_option("xmajorgrids", None)
153
+ if self.layout.yaxis.showgrid:
154
+ self.add_option("ymajorgrids", None)
155
+ if self.layout.xaxis.ticklen is not None:
156
+ self.add_option("tickwidth", self.layout.xaxis.ticklen)
157
+ if self.layout.yaxis.ticklen is not None:
158
+ self.add_option("tickwidth", self.layout.yaxis.ticklen)
159
+
160
+ if (m := self.layout.xaxis.minor) is not None:
161
+ if m.showgrid:
162
+ self.add_option("xminorgrids", None)
163
+ if m.ticklen is not None:
164
+ self.add_option("subtickwidth", m.ticklen)
165
+
166
+
167
+ def treat_background_layout(self, colors_set):
168
+ if self.layout.plot_bgcolor is not None:
169
+ bg_color = convert_color(self.layout.plot_bgcolor)
170
+ colors_set.add(bg_color[:3])
171
+ opacity = bg_color[3]
172
+ self.add_option("axis background/.style", f"{{fill={bg_color[0]}}}")
173
+
174
+ def treat_bar_layout(self):
175
+ if (barmode := self.layout.barmode) is not None:
176
+ if barmode == "stack":
177
+ self.add_option("ybar stacked", None)
@@ -140,6 +140,6 @@ def draw_heatmap(data, fig, img_name, axis: Axis):
140
140
  axis.add_option("tick align", "outside")
141
141
  axis.add_option("tick pos", "left")
142
142
 
143
- code += tex_addplot(img_name, type="graphics", type_options=f"includegraphics cmd=\pgfimage,xmin={xmin}, xmax={xmax}, ymin={ymin}, ymax={ymax}")
143
+ code += tex_addplot(img_name, type="graphics", type_options=f"includegraphics cmd=\\pgfimage,xmin={xmin}, xmax={xmax}, ymin={ymin}, ymax={ymax}")
144
144
 
145
145
  return code
@@ -0,0 +1,132 @@
1
+ from ._axis import Axis
2
+ from ._utils import get_ticks_str, option_dict_to_str
3
+ from ._tex import tex_addplot
4
+ from ._color import convert_color
5
+ import numbers
6
+ from warnings import warn
7
+
8
+ def formalize_data(data, axis:Axis, row_sep="\\\\"):
9
+ """Formalize the data for the histogram trace.
10
+
11
+ Parameters
12
+ ----------
13
+ data
14
+ data from Plotly figure
15
+
16
+ Returns
17
+ -------
18
+ formalized data
19
+ """
20
+
21
+ data_str = f"data{row_sep}\n"
22
+ if isinstance(data[0], numbers.Number):
23
+ for x in data:
24
+ data_str += "{}{} ".format(x, row_sep)
25
+
26
+ else:
27
+ flag_axis = True
28
+ if axis.xticks is not None:
29
+ flag_axis = False
30
+ data_names = axis.xticks
31
+ else: data_names = []
32
+
33
+ for x in data:
34
+ if x not in data_names:
35
+ data_names.append(x)
36
+ data_str += f"{data_names.index(x)}{row_sep} "
37
+ if flag_axis:
38
+ ticks, ticklabels = get_ticks_str(data_names)
39
+ axis.add_option("xtick", ticks)
40
+ axis.add_option("xticklabels", ticklabels)
41
+
42
+
43
+ return data_str
44
+
45
+ def draw_histogram(trace, axis: Axis, colors_set, row_sep="\\\\"):
46
+ """
47
+ Draw a histogram and return the TikZ code.
48
+
49
+ Parameters
50
+ ----------
51
+ trace : plotly.graph_objs._histogram.Histogram
52
+ The histogram trace object containing data and style information.
53
+ axis : Axis
54
+ The axis object to which the histogram will be added.
55
+ colors_set : set
56
+ A set to keep track of colors used in the plot.
57
+ row_sep : str, optional
58
+ The row separator for the data table in TikZ, by default "\\\\"
59
+
60
+ Returns
61
+ -------
62
+ str
63
+ A string containing the TikZ code for the histogram.
64
+
65
+ Notes
66
+ -----
67
+ - The function currently does not support 'percent', 'probability', and 'density' normalization for histograms.
68
+ - Text templates are not supported.
69
+ - Only 'count' aggregation function is supported; other functions need pre-treatment of data.
70
+ """
71
+
72
+ code = ""
73
+
74
+ plot_options = {"hist": None}
75
+ type_options = {"row sep": row_sep, "y index": 0}
76
+ hist_options = {}
77
+
78
+ if trace.x is not None:
79
+ data_str = formalize_data(trace.x, axis, row_sep=row_sep)
80
+ axis.add_option("ybar", None)
81
+ elif trace.y is not None:
82
+ data_str = formalize_data(trace.y, axis, row_sep=row_sep)
83
+ axis.add_option("ybar", None)
84
+ axis.add_option("x filter/.expression", "rawy")
85
+ axis.add_option("y filter/.expression", "rawx")
86
+ hist_options["handler/.style"] = "{xbar interval}"
87
+
88
+
89
+
90
+ if trace.nbinsx is not None:
91
+ hist_options["bins"] = trace.nbinsx
92
+
93
+ if trace.histnorm == "percent":
94
+ warn(f"Sorry, I did not found an equivalent for histnorm='{trace.histnorm}' in Tikz. I you need this feature implemented, please open an issue, if possible with a general pgfplots code that would plot this :).\nFor now, the histogram will be plotted without normalization (as if histnorm='probability density').")
95
+ hist_options["density"] = None
96
+ elif trace.histnorm == "probability":
97
+ warn(f"Sorry, I did not found an equivalent for histnorm='{trace.histnorm}' in Tikz. I you need this feature implemented, please open an issue, if possible with a general pgfplots code that would plot this :).\nFor now, the histogram will be plotted without normalization (as if histnorm='probability density').")
98
+ hist_options["density"] = None
99
+ elif trace.histnorm == "density":
100
+ warn(f"Sorry, I did not found an equivalent for histnorm='{trace.histnorm}' in Tikz. I you need this feature implemented, please open an issue, if possible with a general pgfplots code that would plot this :).\nFor now, the histogram will be plotted without normalization (as if histnorm='probability density').")
101
+ hist_options["density"] = None
102
+ elif trace.histnorm == "probability density":
103
+ hist_options["density"] = None
104
+
105
+ if trace.cumulative.enabled:
106
+ hist_options["cumulative"] = None
107
+
108
+ if bool(hist_options):
109
+ plot_options["hist"] = f"{{{option_dict_to_str(hist_options)}}}"
110
+
111
+ if trace.texttemplate is not None:
112
+ warn("Text template is not supported yet.")
113
+
114
+ if (m := trace.marker) is not None:
115
+
116
+ if (c := m.color) is not None:
117
+ colors_set.add(convert_color(c))
118
+ plot_options["fill"] = convert_color(c)[0]
119
+ plot_options["color"] = convert_color(c)[0]
120
+
121
+ if m.opacity is not None:
122
+ plot_options["opacity"] = m.opacity
123
+
124
+ if (f := trace.histfunc) is not None:
125
+ if f != "count":
126
+ warn("To the best of our knowledge, other aggregate function than 'count' are not supported in pgfplots. Please pre-treat your data to display what you want. Sorry for the inconvenience.")
127
+
128
+ code += tex_addplot(data_str, type="table",
129
+ options=option_dict_to_str(plot_options), type_options=option_dict_to_str(type_options))
130
+
131
+
132
+ return code
@@ -3,6 +3,7 @@ from .__about__ import __version__
3
3
  from ._tex import *
4
4
  from ._scatter import draw_scatter2d
5
5
  from ._heatmap import draw_heatmap
6
+ from ._histogram import draw_histogram
6
7
  from ._axis import Axis
7
8
  from ._color import *
8
9
  from ._annotations import str_from_annotation
@@ -45,11 +46,6 @@ def get_tikz_code(
45
46
  axis = Axis(figure_layout, colors_set, axis_options=axis_options)
46
47
  data_container = DataContainer()
47
48
 
48
- if figure_layout.xaxis.showline == False:
49
- axis.add_option("axis x line", "none")
50
- if figure_layout.yaxis.showline == False:
51
- axis.add_option("axis y line", "none")
52
-
53
49
  if len(figure_data) == 0:
54
50
  warn("No data in figure.")
55
51
 
@@ -84,6 +80,12 @@ def get_tikz_code(
84
80
  continue
85
81
  data_str.append( draw_heatmap(trace, fig, img_name, axis) )
86
82
 
83
+ elif trace.type == "histogram":
84
+
85
+ data_str.append( draw_histogram(trace, axis, colors_set) )
86
+ if trace.name and trace['showlegend'] != False:
87
+ data_str.append( tex_add_legendentry(sanitize_TeX_text(trace.name)) )
88
+
87
89
  else:
88
90
  warn(f"Trace type {trace.type} is not supported yet.")
89
91
 
@@ -101,12 +103,12 @@ def get_tikz_code(
101
103
 
102
104
  code += tex_begin_environment("tikzpicture", stack_env, options=tikz_options)
103
105
 
104
- code += "\n"
106
+ if bool(colors_set): code += "\n"
105
107
  color_list = list(colors_set)
106
108
  color_list.sort()
107
109
  for color in color_list:
108
110
  code += tex_add_color(color[0], color[1], color[2])
109
- code += "\n"
111
+ if bool(colors_set): code += "\n"
110
112
 
111
113
  code += axis.open_environment(stack_env)
112
114
 
@@ -1,4 +1,5 @@
1
1
  from ._color import hex2rgb
2
+ from ._utils import sanitize_text
2
3
 
3
4
  def tex_comment(text):
4
5
  """Create a LaTeX comment.
@@ -186,12 +187,40 @@ def tex_create_document(document_class="article", options=None, compatibility="n
186
187
  code += "\\pgfplotsset{compat=" + compatibility + "}\n\n"
187
188
  return code
188
189
 
190
+ # Pushed by error, not used yet
191
+ # def tex_add_fill_pattern(name, pattern, color, options=None):
192
+ # """Create a LaTeX fill pattern.
193
+
194
+ # Parameters
195
+ # ----------
196
+ # pattern
197
+ # pattern name
198
+ # color
199
+ # color string
200
+ # options, optional
201
+ # options given to the pattern, by default None
202
+
203
+ # Returns
204
+ # -------
205
+ # LaTeX code for the fill pattern
206
+ # """
207
+ # print("pattern: ", pattern)
208
+ # code = f"\\addplot[color={color}]\n"
209
+ # code += "fill between[of={name} and axis"
210
+ # if options is not None:
211
+ # code += f",{options}"
212
+ # code += f"];\n"
213
+ # return code
214
+
215
+
216
+
189
217
 
190
218
  def tex_text(text):
191
219
  """Convert a string to LaTeX,
192
220
  escaping the special characters %, _, &, #, $, {, }, ~.
193
221
  """
194
- return text.replace("%", "\\%").replace("_", "\\_").replace("&", "\\&").replace("#", "\\#").replace("$", "\\$").replace("{", "\\{").replace("}", "\\}").replace("~", "\\textasciitilde ")
222
+ text_san = sanitize_text(text, keep_space=True)
223
+ return text_san.replace("%", "\\%").replace("_", "\\_").replace("&", "\\&").replace("#", "\\#").replace("$", "\\$").replace("{", "\\{").replace("}", "\\}").replace("~", "\\textasciitilde ")
195
224
 
196
225
  def get_tikz_colorscale(colorscale, name="mycolor"):
197
226
 
@@ -41,10 +41,36 @@ def replace_all_mounts(text):
41
41
  return pattern_mounts.sub(lambda m: rep_mounts[re.escape(m.group(0))], text)
42
42
 
43
43
 
44
- def sanitize_text(text: str):
45
- return "".join(map(sanitize_char, text))
44
+ def sanitize_text(text: str, keep_space=False):
45
+ """
46
+ Sanitize the input text by removing or replacing unwanted characters.
47
+
48
+ Parameters
49
+ ----------
50
+ text (str): The input text to be sanitized.
51
+ keep_space (bool, optional): If True, spaces will be preserved in the sanitized text.
52
+ If False, spaces will be removed. Defaults to False.
53
+
54
+ Returns
55
+ -------
56
+ str: The sanitized text.
57
+ """
58
+ return "".join(sanitize_char(ch, keep_space=keep_space) for ch in text)
59
+
60
+ def sanitize_char(ch, keep_space=False):
61
+ """
62
+ Sanitize a character by converting it to a hexadecimal representation if it is a special character, non-ASCII, or non-printable.
63
+
64
+ Parameters
65
+ ----------
66
+ ch (str): The character to sanitize.
67
+ keep_space (bool, optional): If True, spaces will be kept as is. Defaults to False.
46
68
 
47
- def sanitize_char(ch):
69
+ Returns
70
+ -------
71
+ str: The sanitized character or its hexadecimal representation.
72
+ """
73
+ if keep_space and ch == " ": return " "
48
74
  if ch in "[]{}= ": return f"x{ord(ch):x}"
49
75
  # if not ascii, return hex
50
76
  if ord(ch) > 127: return f"x{ord(ch):x}"
@@ -113,7 +139,26 @@ def option_dict_to_str(options_dict, sep=" "):
113
139
  return None
114
140
  return options.strip()[:-1]
115
141
 
116
- def get_ticks_str(data, nticks):
142
+ def get_ticks_str(data, nticks=None):
143
+ """Get the ticks and ticklabels for the axis.
144
+
145
+ Parameters
146
+ ----------
147
+ data
148
+ data to be plotted
149
+ nticks
150
+ number of ticks
151
+
152
+ Returns
153
+ -------
154
+ tuple
155
+ ticks and ticklabels
156
+
157
+ Examples
158
+ --------
159
+ >>> get_ticks_str(["Sun", "Sat", "Thur", "Fri"])
160
+ ({0,1,2,3}, {"Sun", "Sat", "Thur", "Fri"})
161
+ """
117
162
  indices = np.arange(len(data))
118
163
  if nticks is not None:
119
164
  data_ = data[::len(data)//(nticks-1)]
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: tikzplotly
3
- Version: 0.1.7
3
+ Version: 0.2.0
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)
@@ -30,9 +30,11 @@ Keywords: plotly,latex,tikz,figure,conversion,graphics
30
30
  Classifier: Development Status :: 5 - Production/Stable
31
31
  Classifier: Programming Language :: Python
32
32
  Classifier: Programming Language :: Python :: 3
33
- Classifier: Programming Language :: Python :: 3.8
34
33
  Classifier: Programming Language :: Python :: 3.9
35
34
  Classifier: Programming Language :: Python :: 3.10
35
+ Classifier: Programming Language :: Python :: 3.11
36
+ Classifier: Programming Language :: Python :: 3.12
37
+ Classifier: Programming Language :: Python :: 3.13
36
38
  Classifier: License :: OSI Approved :: MIT License
37
39
  Classifier: Operating System :: OS Independent
38
40
  Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
@@ -45,11 +47,15 @@ Requires-Dist: pillow
45
47
  Provides-Extra: dev
46
48
  Requires-Dist: pandas; extra == "dev"
47
49
 
48
- ![logo-tikzplotly](https://raw.githubusercontent.com/thomas-saigre/tikzplotly/main/doc/img/logo.svg "Tikzplotly")
50
+ ![logo-tikzplotly](https://raw.githubusercontent.com/thomas-saigre/tikzplotly/main/docs/assets/logo.svg "Tikzplotly")
51
+
49
52
 
50
53
  [![PyPi Version](https://img.shields.io/pypi/v/tikzplotly.svg?style=flat-square)](https://pypi.org/project/tikzplotly)
54
+ [![PyPi Downloads](https://img.shields.io/pypi/dm/tikzplotly.svg?style=flat-square)](https://pypi.org/project/tikzplotly)
55
+ [![PyPi License](https://img.shields.io/pypi/l/tikzplotly.svg?style=flat-square)](https://pypi.org/project/tikzplotly)
51
56
  [![PyPI pyversions](https://img.shields.io/pypi/pyversions/tikzplotly.svg?style=flat-square)](https://pypi.org/pypi/tikzplotly/)
52
57
 
58
+ [![GitHub release](https://img.shields.io/github/release/thomas-saigre/tikzplotly.svg?style=flat-square)](https://github.com/thomas-saigre/tikzplotly/releases)
53
59
  [![Issue](https://img.shields.io/github/issues-raw/thomas-saigre/tikzplotly?style=flat-square)](https://github.com/thomas-saigre/tikzplotly/issues)
54
60
  [![codecov](https://img.shields.io/codecov/c/github/thomas-saigre/tikzplotly.svg?style=flat-square)](https://codecov.io/gh/thomas-saigre/tikzplotly)
55
61
  ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/thomas-saigre/tikzplotly/ci.yml?style=flat-square)
@@ -142,8 +148,7 @@ To correctly compile the document, you will need to add the following packages t
142
148
 
143
149
  * This module is in development and new features are added bit by bit, when needed. If you have a feature request, please open an issue with the plotly figure you want to convert and the desired output.
144
150
  You can also submit a pull request with the desired feature !
145
- * Some feature can result in different output between the plotly figure and the tikz figure, for instance the size of markers.
146
- * More details in [doc/NB.md](doc/NB.md).
151
+ * Some feature can result in different output between the plotly figure and the tikz figure, for instance the size of markers, more details can be found in the documentation.
147
152
 
148
153
 
149
154
  ## References
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  src/tests/test_heatmaps.py
5
+ src/tests/test_histograms.py
5
6
  src/tests/test_line_charts.py
6
7
  src/tests/test_markers.py
7
8
  src/tikzplotly/__about__.py
@@ -13,6 +14,7 @@ src/tikzplotly/_dash.py
13
14
  src/tikzplotly/_data.py
14
15
  src/tikzplotly/_dataContainer.py
15
16
  src/tikzplotly/_heatmap.py
17
+ src/tikzplotly/_histogram.py
16
18
  src/tikzplotly/_marker.py
17
19
  src/tikzplotly/_save.py
18
20
  src/tikzplotly/_scatter.py
@@ -24,6 +26,7 @@ src/tikzplotly.egg-info/dependency_links.txt
24
26
  src/tikzplotly.egg-info/requires.txt
25
27
  src/tikzplotly.egg-info/top_level.txt
26
28
  tests/test_heatmap.py
29
+ tests/test_histograms.py
27
30
  tests/test_markers.py
28
31
  tests/test_scatter.py
29
32
  tests/test_tikzplotly.py
@@ -0,0 +1,95 @@
1
+ import plotly.express as px
2
+ import plotly.graph_objects as go
3
+ import numpy as np
4
+ import os
5
+ from .helpers import assert_equality
6
+ import pathlib
7
+ import pytest
8
+
9
+ this_dir = pathlib.Path(__file__).resolve().parent
10
+ test_name = "test_histograms"
11
+ pathlib.Path("/tmp/tikzplotly").mkdir(parents=True, exist_ok=True)
12
+
13
+ def plot_1():
14
+ df = px.data.tips()
15
+ fig = px.histogram(df, x="total_bill", nbins=20)
16
+ return fig
17
+
18
+ def plot_2():
19
+ np.random.seed(0)
20
+ y = np.random.randn(100)
21
+ fig = go.Figure(data=[go.Histogram(y=y)])
22
+ return fig
23
+
24
+ def plot_3():
25
+ df = px.data.tips()
26
+ fig = px.histogram(df, x="day", category_orders=dict(day=["Thur", "Fri", "Sat", "Sun"]), opacity=0.8)
27
+ return fig
28
+
29
+ def plot_4(histnorm):
30
+ np.random.seed(0)
31
+ x = np.random.randn(100)
32
+ fig = go.Figure(data=[go.Histogram(x=x, histnorm=histnorm)])
33
+ return fig
34
+
35
+ def plot_5():
36
+ np.random.seed(0)
37
+ x = np.random.randn(100)
38
+ fig = go.Figure(data=[go.Histogram(x=x, cumulative_enabled=True)])
39
+ return fig
40
+
41
+ def plot_6():
42
+ np.random.seed(0)
43
+ x0 = np.random.randn(100)
44
+ x1 = np.random.randn(100)
45
+
46
+ fig = go.Figure()
47
+ fig.add_trace(go.Histogram(x=x0))
48
+ fig.add_trace(go.Histogram(x=x1))
49
+
50
+ fig.update_layout(barmode='stack')
51
+ return fig
52
+
53
+ def plot_7():
54
+ df = px.data.tips()
55
+ fig = px.histogram(df, x="total_bill", y="tip", histfunc="avg", nbins=8, text_auto=True)
56
+ return fig
57
+
58
+ def plot_8():
59
+ df = px.data.tips()
60
+ fig = px.histogram(df, x="total_bill", color="sex")
61
+ return fig
62
+
63
+ def plot_9():
64
+ np.random.seed(0)
65
+ x_data = np.random.choice(["A", "B", "C", "D"], size=20, p=[0.4, 0.2, 0.3, 0.1])
66
+ fig = px.histogram(x=x_data)
67
+ return fig
68
+
69
+ def test_1():
70
+ assert_equality(plot_1(), os.path.join(this_dir, test_name, test_name + "_1_reference.tex"))
71
+
72
+ def test_2():
73
+ assert_equality(plot_2(), os.path.join(this_dir, test_name, test_name + "_2_reference.tex"))
74
+
75
+ def test_3():
76
+ assert_equality(plot_3(), os.path.join(this_dir, test_name, test_name + "_3_reference.tex"))
77
+
78
+ @pytest.mark.parametrize("histnorm", ["percent", "probability", "density", "probability density"])
79
+ def test_4(histnorm):
80
+ assert_equality(plot_4(histnorm), os.path.join(this_dir, test_name, test_name + "_4_reference.tex"))
81
+
82
+ def test_5():
83
+ assert_equality(plot_5(), os.path.join(this_dir, test_name, test_name + "_5_reference.tex"))
84
+
85
+ def test_6():
86
+ assert_equality(plot_6(), os.path.join(this_dir, test_name, test_name + "_6_reference.tex"))
87
+
88
+ def test_7():
89
+ assert_equality(plot_7(), os.path.join(this_dir, test_name, test_name + "_7_reference.tex"))
90
+
91
+ def test_8():
92
+ assert_equality(plot_8(), os.path.join(this_dir, test_name, test_name + "_8_reference.tex"))
93
+
94
+ def test_9():
95
+ assert_equality(plot_9(), os.path.join(this_dir, test_name, test_name + "_9_reference.tex"))
@@ -28,7 +28,7 @@ def plot_3():
28
28
  return fig
29
29
 
30
30
  def plot_4():
31
- labels = ['Television', 'Newspaper', 'Internet', 'Radio']
31
+ labels = ['𝕋elevision', 'Newspaper', 'Internet', 'Radio']
32
32
  colors = ['rgb(67,67,67)', 'rgb(115,115,115)', 'rgb(49,130,189)', 'rgb(189,189,189)']
33
33
 
34
34
  mode_size = [4, 4, 6, 4]
@@ -62,7 +62,7 @@ def plot_4():
62
62
 
63
63
  fig.update_layout(
64
64
  xaxis=dict(
65
- showline=True,
65
+ showline=False,
66
66
  showgrid=False,
67
67
  showticklabels=True,
68
68
  linecolor='rgb(204, 204, 204)',
@@ -143,6 +143,63 @@ def plot_6(x=True, y=True):
143
143
  if y: fig.data[0].y = None
144
144
  return fig
145
145
 
146
+ def plot_7():
147
+ month = ['January', 'February', 'March', 'April', 'May', 'June', 'July',
148
+ 'August', 'September', 'October', 'November', 'December']
149
+ high_2000 = [32.5, 37.6, 49.9, 53.0, 69.1, 75.4, 76.5, 76.6, 70.7, 60.6, 45.1, 29.3]
150
+ low_2000 = [13.8, 22.3, 32.5, 37.2, 49.9, 56.1, 57.7, 58.3, 51.2, 42.8, 31.6, 15.9]
151
+ high_2007 = [36.5, 26.6, 43.6, 52.3, 71.5, 81.4, 80.5, 82.2, 76.0, 67.3, 46.1, 35.0]
152
+ low_2007 = [23.6, 14.0, 27.0, 36.8, 47.6, 57.7, 58.9, 61.2, 53.3, 48.5, 31.0, 23.6]
153
+ high_2014 = [28.8, 28.5, 37.0, 56.8, 69.7, 79.7, 78.5, 77.8, 74.1, 62.6, 45.3, 39.9]
154
+ low_2014 = [12.7, 14.3, 18.6, 35.5, 49.9, 58.0, 60.0, 58.6, 51.7, 45.2, 32.2, 29.1]
155
+
156
+ fig = go.Figure()
157
+ # Create and style traces
158
+ fig.add_trace(go.Scatter(x=month, y=high_2014, name='High 2014',
159
+ line=dict(color='firebrick', width=1.5)))
160
+ fig.add_trace(go.Scatter(x=month, y=low_2014, name = 'Low 2014',
161
+ line=dict(color='royalblue', width=1.5)))
162
+ fig.add_trace(go.Scatter(x=month, y=high_2007, name='High 2007',
163
+ line=dict(color='firebrick', width=1.5,
164
+ dash='dash') # dash options include 'dash', 'dot', and 'dashdot'
165
+ ))
166
+ fig.add_trace(go.Scatter(x=month, y=low_2007, name='Low 2007',
167
+ line = dict(color='royalblue', width=1.5, dash='dash')))
168
+ fig.add_trace(go.Scatter(x=month, y=high_2000, name='High 2000',
169
+ line = dict(color='firebrick', width=1.5, dash='dot')))
170
+ fig.add_trace(go.Scatter(x=month, y=low_2000, name='Low 2000',
171
+ line=dict(color='royalblue', width=1.5, dash='dot')))
172
+
173
+ fig.update_layout(title='Average High and Low Temperatures in New York',
174
+ xaxis_title='Month',
175
+ yaxis_title='Temperature (degrees F)')
176
+ return fig
177
+
178
+ def plot_8():
179
+ df = px.data.gapminder().query("year == 2007")
180
+ fig = px.scatter(df, x="gdpPercap", y="lifeExp", hover_name="country",
181
+ log_x=True, range_x=[1,100000], range_y=[0,100])
182
+ fig.update_xaxes(showgrid=True, minor=dict(ticks="inside", ticklen=6, showgrid=True), ticklen=6)
183
+ fig.update_yaxes(showgrid=True, ticklen=6)
184
+ return fig
185
+
186
+ def plot_9():
187
+ np.random.seed(0)
188
+ x = np.random.lognormal(mean=0.0, sigma=1.0, size=50)
189
+ y = np.random.lognormal(mean=0.0, sigma=1.0, size=50)
190
+ fig = px.scatter(x=x, y=y, labels={'x':'log(x)', 'y':'log(y)'})
191
+ fig.update_xaxes(type="log", visible=False)
192
+ fig.update_yaxes(type="log", visible=False)
193
+ return fig
194
+
195
+ def plot_10():
196
+ df = px.data.gapminder().query("continent == 'Oceania'")
197
+ fig = px.line(df, x='year', y='lifeExp', color='country', markers=True)
198
+ fig.update_xaxes(autorange="reversed")
199
+ fig.update_yaxes(autorange="reversed")
200
+ return fig
201
+
202
+
146
203
  def test_1():
147
204
  assert_equality(plot_1(), os.path.join(this_dir, test_name, test_name + "_1_reference.tex"))
148
205
 
@@ -160,4 +217,16 @@ def test_5():
160
217
 
161
218
  @pytest.mark.parametrize("x, y", [(True, True), (True, False), (False, True)])
162
219
  def test_6(x, y):
163
- assert_equality(plot_6(x, y), os.path.join(this_dir, test_name, test_name + f"_6_{x}_{y}_reference.tex"))
220
+ assert_equality(plot_6(x, y), os.path.join(this_dir, test_name, test_name + f"_6_{x}_{y}_reference.tex"))
221
+
222
+ def test_7():
223
+ assert_equality(plot_7(), os.path.join(this_dir, test_name, test_name + "_7_reference.tex"))
224
+
225
+ def test_8():
226
+ assert_equality(plot_8(), os.path.join(this_dir, test_name, test_name + "_8_reference.tex"))
227
+
228
+ def test_9():
229
+ assert_equality(plot_9(), os.path.join(this_dir, test_name, test_name + "_9_reference.tex"))
230
+
231
+ def test_10():
232
+ assert_equality(plot_10(), os.path.join(this_dir, test_name, test_name + "_10_reference.tex"))
@@ -0,0 +1,39 @@
1
+ import tikzplotly
2
+ from tikzplotly._tex import *
3
+ import os
4
+ import plotly.express as px
5
+ import pytest
6
+ import pathlib
7
+ from .helpers import compare_two_files
8
+
9
+ this_dir = pathlib.Path(__file__).resolve().parent
10
+
11
+ @pytest.mark.parametrize("axis_options", ["None", "dict", "str"])
12
+ def test_tikzplotly(axis_options):
13
+ fig = px.scatter(x=[1, 2, 3], y=[1, 2, 3])
14
+ if axis_options == "dict":
15
+ tikzplotly.save("/tmp/tikzplotly/test_tikzplotly_dict.tex", fig, axis_options={"xlabel": "x", "ylabel": "y"})
16
+ elif axis_options == "str":
17
+ tikzplotly.save("/tmp/tikzplotly/test_tikzplotly_str.tex", fig, axis_options="xlabel=x, ylabel=y, xmajorgrids")
18
+ else:
19
+ tikzplotly.save("/tmp/tikzplotly/test_tikzplotly_none.tex", fig)
20
+
21
+ def test_create_document():
22
+ main_tex_content = tex_create_document(compatibility="newest")
23
+ main_tex_content += "\\usepackage{graphicx}\n"
24
+ main_tex_content += "\n"
25
+ stack_env = []
26
+ main_tex_content += tex_begin_environment("document", stack_env) + '\n'
27
+
28
+ main_tex_content += tex_begin_environment("figure", stack_env)
29
+ main_tex_content += " \\includegraphics{example-image-a}\n"
30
+ main_tex_content += " \\caption{Test figure}\n"
31
+ main_tex_content += tex_end_environment(stack_env) + '\n'
32
+
33
+ main_tex_content += "\n" + tex_end_all_environment(stack_env)
34
+
35
+ main_tex_path = os.path.join("/tmp/tikzplotly", "test_create_document.tex")
36
+ with open(main_tex_path, "w") as f:
37
+ f.write(main_tex_content)
38
+
39
+ compare_two_files(main_tex_path, os.path.join(this_dir, "test_tikzplotly", "test_create_document.tex"))
@@ -1 +0,0 @@
1
- __version__ = "0.1.7"
@@ -1,6 +0,0 @@
1
- import tikzplotly
2
- import plotly.express as px
3
-
4
- def test_tikzplotly():
5
- fig = px.scatter(x=[1, 2, 3], y=[1, 2, 3])
6
- tikzplotly.save("/tmp/tikzplotly/test_tikzplotly.tex", fig)
File without changes
File without changes