Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macOS-latest]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/setup-python@v3
with:
Expand All @@ -38,7 +38,7 @@ jobs:
pip install tox
tox -- --cov tikzplotly --cov-report xml --cov-report term
- uses: codecov/codecov-action@v5.4.3
if: ${{ matrix.python-version == '3.13' && matrix.os == 'ubuntu-latest' }}
if: ${{ matrix.python-version == '3.14' && matrix.os == 'ubuntu-latest' }}
with:
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true
9 changes: 3 additions & 6 deletions .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
name: Build GitHub Pages
on:
push:
branches:
- main
paths:
- 'docs/**'
release:
types: [published]
workflow_dispatch:
permissions:
contents: write
Expand All @@ -22,7 +19,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: 3.9
python-version: 3.14
- run: pip install \
mkdocs-material[recommended] mkdocs-git-revision-date-localized-plugin
- run: mkdocs gh-deploy --config-file mkdocs.yml --force
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

# Tikzplotly

Convert [plotly](https://plotly.com/python/) figures to TikZ code for inclusion into [PGFPlots](https://www.ctan.org/pkg/pgfplots) ([PGF/TikZ](https://www.ctan.org/pkg/pgf)) figures.
Convert [Plotly](https://plotly.com/python/) figures to TikZ code for inclusion into [PGFPlots](https://www.ctan.org/pkg/pgfplots) ([PGF/TikZ](https://www.ctan.org/pkg/pgf)) figures.

This results in a ti*k*z code, that can be easily included into your LaTeX document.
This also allows to easily edit the content of the figure.
Expand Down Expand Up @@ -101,9 +101,9 @@ To correctly compile the document, you will need to add the following packages t

## Note

* This module is in development and new features are added bit by bit, when needed. If you have a feature request, please open an issue with the plotly figure you want to convert and the desired output.
* This module is in development and new features are added bit by bit, when needed. If you have a feature request, please open an issue with the Plotly figure you want to convert and the desired output.
You can also submit a pull request with the desired feature !
* Some feature can result in different output between the plotly figure and the TikZ figure, for instance the size of markers, more details can be found in [the documentation](https://thomas-saigre.github.io/tikzplotly/).
* Some feature can result in different output between the Plotly figure and the TikZ figure, for instance the size of markers, more details can be found in [the documentation](https://thomas-saigre.github.io/tikzplotly/).


## References
Expand Down
14 changes: 7 additions & 7 deletions docs/plot/NB.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Some differences between plotly ans pgfplots
# Some differences between Plotly ans pgfplots

We gather here some points that are different between a plotly figure and the corresponding Ti*k*Z figure generated with `tikzplotly`.
We gather here some points that are different between a Plotly figure and the corresponding Ti*k*Z figure generated with `tikzplotly`.

* Size of the objects: in plotly, the size is given in `px` unit, while in Ti*k*Z it is given in `pt`. A conversion is performed (`1 px = 0.75 pt`), but this still results in objects of different sizes.
* By default, the colors or the markers are not the same in plotly and pgfplots. For instance, if nothing is specified, plotly will always use a dot marker, while pgfplot will change for each trace.
* The order of displaying the traces may be unconsistent between plotly and pgfplots. For instance, for [this example](https://plotly.com/python/histograms/#several-histograms-for-the-different-values-of-one-column), the two traces are inverted.
* The angle of rotation is different between Plotly and Ti*k*Z, but the function Plotly ↦ Ti*k*Z is not know at this current point.
* When tricky names are used in symbolic expression (such as names with a space within), the space is removed by tisk plotly (*e.g.* the text `United Kingdom` in Plotly will be exported as `UnitedKindgon` in Ti*k*Z), fill free to update the exported file to render the figure you wish!
* Size of the objects: in Plotly, the size is given in `px` unit, while in Ti*k*Z it is given in `pt`. A conversion is performed (`1 px = 0.75 pt`), but this still results in objects of different sizes.
* By default, the colors or the markers are not the same in Plotly and pgfplots. For instance, if nothing is specified, Plotly will always use a dot marker, while pgfplots will change for each trace.
* The order of displaying the traces may be inconsistent between Plotly and pgfplots. For instance, for [this example](https://Plotly.com/python/histograms/#several-histograms-for-the-different-values-of-one-column), the two traces are inverted.
* The angle of rotation is different between Plotly and Ti*k*Z, but the function Plotly ↦ Ti*k*Z is not known at this current point.
* When tricky names are used in symbolic expression (such as names with a space within), the space is removed by tikzplotly (*e.g.* the text `United Kingdom` in Plotly will be exported as `UnitedKindgon` in Ti*k*Z), fill free to update the exported file to render the figure you wish!
7 changes: 4 additions & 3 deletions docs/plot/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

From [Styling Markers in Python](https://plotly.com/python/marker-style/).

??? example "Marker Style Example"
??? Example "Marker Style Example"

```python
import plotly.express as px
import tikzplotly
Expand All @@ -26,6 +27,6 @@ From [Styling Markers in Python](https://plotly.com/python/marker-style/).


!!! Note
- There are somes markers implemented in plotly that are not available in pgfplots (or at least not referenced in the [documentation](https://tikz.dev/pgfplots/reference-markers)). For more details, refer to the example [test_markers](https://github.com/thomas-saigre/tikzplotly/blob/main/tests/test_markers.py). By defualt, the marker style `*` will be used.
- By default, the colors or the markers are not the same in plotly and pgfplots. For instance, if nothing is specified, plotly will always use a dot marker, while pgfplot will change for each trace.
- There are some markers implemented in Plotly that are not available in pgfplots (or at least not referenced in the [documentation](https://tikz.dev/pgfplots/reference-markers)). For more details, refer to the example [test_markers](https://github.com/thomas-saigre/tikzplotly/blob/main/tests/test_markers.py). By default, the marker style `*` will be used.
- By default, the colors or the markers are not the same in Plotly and pgfplots. For instance, if nothing is specified, Plotly will always use a dot marker, while pgfplots will change for each trace.
- The angle of rotation is different between Plotly and Ti*k*Z.
22 changes: 14 additions & 8 deletions docs/plot/supported.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The plots type supported by tikzplotly are presented in this page.

The code has been constructed to export (almost all) the figures of the page [Line Charts in Python](https://plotly.com/python/line-charts/) of Plotly documentation.

??? example "Scatter plot Example"
??? Example "Scatter plot Example"

```python
import plotly.express as px
Expand All @@ -31,7 +31,8 @@ The code has been constructed to export (almost all) the figures of the page [Li

The code has been constructed to export (almost all) the figures of the page [Heatmaps in Python](https://plotly.com/python/heatmaps/) of Plotly documentation.

??? example "Heatmap Example"
??? Example "Heatmap Example"

```python
import plotly.express as px
import tikzplotly
Expand All @@ -44,14 +45,15 @@ The code has been constructed to export (almost all) the figures of the page [He
![Heatmap Example](../assets/examples/heatmap.png)

!!! Note
- If possible, TikzPlotly try to save the heatmap as a png of the smallest size possible, namely 1 pixel for each value of the heatmap. But in some case, such export does not work. In this case, the image is saved in the original size of the Plotly figure.
- If possible, TikzPlotly try to save the heatmap as a PNG of the smallest size possible, namely 1 pixel for each value of the heatmap. But in some case, such export does not work. In this case, the image is saved in the original size of the Plotly figure.


## Histograms

The examples of the page [Histograms in Python](https://plotly.com/python/histograms/) of Plotly documentation are supported.

??? example "Histogram Example"
??? Example "Histogram Example"

```python
df = px.data.tips()
fig = px.histogram(df, x="total_bill")
Expand All @@ -68,7 +70,8 @@ The examples of the page [Histograms in Python](https://plotly.com/python/histog

Some examples of the page [Bar Charts in Python](https://plotly.com/python/bar-charts/) of Plotly documentation are supported (not stacked and aggregated bars).

??? example "Bar plot"
??? Example "Bar plot"

```python
wide_df = px.data.medals_wide()
fig = px.bar(
Expand All @@ -88,7 +91,8 @@ Some examples of the page [Bar Charts in Python](https://plotly.com/python/bar-c
The examples from the pages [Polar Charts in Python](https://plotly.com/python/polar-chart/) and [Radar Charts in Python](https://plotly.com/python/radar-chart/) are supported.


??? example "Polar plot"
??? Example "Polar plot"

```python
df = px.data.wind()
fig = px.line_polar(df, r="frequency", theta="direction", color="strength", line_close=True,
Expand All @@ -99,7 +103,8 @@ The examples from the pages [Polar Charts in Python](https://plotly.com/python/p
![Polar plot Example](../assets/examples/polar.png)


??? example "Radar plot"
??? Example "Radar plot"

```python
df = pd.DataFrame(dict(
r=[1, 5, 2, 2, 3],
Expand All @@ -116,7 +121,8 @@ The examples from the pages [Polar Charts in Python](https://plotly.com/python/p
Examples from [3D Scatter Plots in Python ](https://plotly.com/python/3d-scatter-plots/) can be exported with tikzplotly.


??? example "3D scatter plot"
??? Example "3D scatter plot"

```python
df = px.data.iris()
fig = px.scatter_3d(df, x='sepal_length', y='sepal_width', z='petal_width', color='species')
Expand Down
13 changes: 7 additions & 6 deletions docs/plot/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ tikzplotly.save("example.tex", fig)

The arguments of the function `tikzplotly.save` are:

* `filename` (str): The name of the file where the ti*k*z code will be saved.
* `fig` (plotly.graph_objs.Figure): The figure to be saved.
* `tikz_options` (str, optional): The options to be passed to the `tikzpicture` environment. Default is `None`.
* `filename` (`str`): The name of the file where the ti*k*z code will be saved.
* `fig` (`plotly.graph_objs.Figure`): The figure to be saved.
* `tikz_options` (`str`, optional): The options to be passed to the `tikzpicture` environment. Default is `None`.
For example `tikz_options="scale=0.5"` will scale the figure by a factor 0.5.
* `axis_options` (str, optional): Option that you would like to manually add the the `axis` environment.
* `include_disclamer` (bool, optional): If `True`, the line `% This file was created with tikzplotly version XXX.` is added at the head of the generated code. Default is `True`.
* `img_name` (str, optional): only for the export of [heatmaps](supported.md#heat-maps), the name of the image that will be saved. Default is `heatmap.png`.
* `axis_options` (`str`, optional): Option that you would like to manually add to the `axis` environment.
* `include_disclamer` (`bool`, optional): If `True`, the line `% This file was created with tikzplotly version XXX.` is added at the head of the generated code. Default is `True`.
* `img_name` (`str`, optional): only for the export of [heatmaps](supported.md#heat-maps), the name of the image that will be saved. Default is `heatmap.png`.
* `decimate` (`int|None`, optional): only for the exports of scatter plots (in [2D](supported.md#scatter-plots) and [3D](supported.md#3d-scatter-plots)), reduce the number of data exported to only get the `decimate`-th ones (and the last one). If `None`, then all the data are exported. This is the default behavior.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ classifiers = [
"Development Status :: 5 - Production/Stable",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Topic :: Multimedia :: Graphics :: Graphics Conversion",
Expand Down
57 changes: 47 additions & 10 deletions src/tikzplotly/_data_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class Data:
"""Class to handle data in TikZ plots.
"""

def __init__(self, name, x):
def __init__(self, name, x, decimate: int|None=None):
"""Initialize a Data object.

Parameters
Expand All @@ -59,6 +59,8 @@ def __init__(self, name, x):
self.name = name
self.macro_name = "\\" + replace_all_digits(name)
self.x = x
# decimate: None means keep all points, otherwise integer >= 1
self.decimate = decimate
self.y_label = []
self.y_data = []

Expand All @@ -84,7 +86,7 @@ def add_y_data(self, y, y_label=None):
class Data3D:
"""Handle 3D data in Tikz plots
"""
def __init__(self, x, y, z, name):
def __init__(self, x, y, z, name, decimate: int|None=None):
"""Initialize the Data3D object

Parameters
Expand All @@ -101,6 +103,8 @@ def __init__(self, x, y, z, name):
self.x = np.array(x)
self.y = np.array(y)
self.z = np.array(z)
# decimate: None means keep all points, otherwise integer >= 1
self.decimate = decimate
if name:
self.name = sanitize_text(name, keep_space=0)
else:
Expand Down Expand Up @@ -128,7 +132,7 @@ class DataContainer:
def __init__(self):
self.data = []

def add_data(self, x, y, name=None, y_label=None):
def add_data(self, x, y, name=None, y_label=None, decimate: int|None=None):
"""Add data to the container.

Parameters
Expand All @@ -141,11 +145,16 @@ def add_data(self, x, y, name=None, y_label=None):
name of the y data, by default None
name, optional
name of the data, by default None
decimate, optional
number of data to keep, all of them if None, by default None.

Returns
-------
tuple (macro_name, y_label), where macro_name is the name of the data in LaTeX and y_label the name of the y data in LaTeX
"""
if decimate is not None and (not isinstance(decimate, int) or decimate < 1):
raise ValueError("decimate must be None or an integer >= 1")

for data in self.data:
if len(data.x) != len(x):
continue
Expand All @@ -157,12 +166,12 @@ def add_data(self, x, y, name=None, y_label=None):
elif hasattr(are_equals, "all") and are_equals.all():
y_label_val = data.add_y_data(y, y_label or name)
return data.macro_name, treat_data(y_label_val)
data_to_add = Data(f"data{index_to_letters(len(self.data))}", x)
data_to_add = Data(f"data{index_to_letters(len(self.data))}", x, decimate=decimate)
y_label_val = data_to_add.add_y_data(y, y_label or name)
self.data.append(data_to_add)
return data_to_add.macro_name, treat_data(y_label_val)

def add_data3d(self, x, y, z, name=None):
def add_data3d(self, x, y, z, name=None, decimate: int|None=None):
"""Add data to the container.

Parameters
Expand All @@ -175,16 +184,21 @@ def add_data3d(self, x, y, z, name=None):
z values of the data
name, optional
name of the data, by default None
decimate, optional
number of data to keep, all of them if None, by default None.

Returns
-------
tuple (macro_name, z_name), where macro_name is the name of the data in LaTeX and z_name the name of the z data in LaTeX
"""
if decimate is not None and (not isinstance(decimate, int) or decimate < 1):
raise ValueError("decimate must be None or an integer >= 1")

for data in self.data:
if hasattr(data, "x") and hasattr(data, "y") and hasattr(data, "z"):
if np.array_equal(data.x, x) and np.array_equal(data.y, y) and np.array_equal(data.z, z):
return data.name, data.z_name
data_obj = Data3D(x, y, z, name)
data_obj = Data3D(x, y, z, name, decimate=decimate)
self.data.append(data_obj)
return data_obj.name

Expand All @@ -202,8 +216,20 @@ def export_data(self):
if hasattr(data, "z"):
export_string += "\\pgfplotstableread{\n"
export_string += "x y z\n"
for x, y, z in zip(data.x, data.y, data.z):
export_string += f"{treat_data(x)} {treat_data(y)} {treat_data(z)}\n"
n = len(data.x)
if getattr(data, 'decimate', None) is None:
indices = range(n)
else:
d = data.decimate
if d <= 1:
indices = range(n)
else:
# always include first and last
indices = list(range(0, n, d))
if indices[-1] != n-1:
indices.append(n-1)
for i in indices:
export_string += f"{treat_data(data.x[i])} {treat_data(data.y[i])} {treat_data(data.z[i])}\n"
export_string += f"}}{{\\{data.name}}}\n"

# 2D
Expand All @@ -216,8 +242,19 @@ def export_data(self):
else:
header += " y"
export_string += header + "\n"
for i, x in enumerate(data.x):
row = [treat_data(x)]
n = len(data.x)
if getattr(data, 'decimate', None) is None:
indices = range(n)
else:
d = data.decimate
if d <= 1:
indices = range(n)
else:
indices = list(range(0, n, d))
if indices[-1] != n-1:
indices.append(n-1)
for i in indices:
row = [treat_data(data.x[i])]
for y_col in data.y_data:
row.append(treat_data(y_col[i]))
export_string += " ".join(row) + "\n"
Expand Down
Loading
Loading