diff --git a/doc/source/changes/version_0_33.rst.inc b/doc/source/changes/version_0_33.rst.inc
index 1f3a3b71b..4a841ab2a 100644
--- a/doc/source/changes/version_0_33.rst.inc
+++ b/doc/source/changes/version_0_33.rst.inc
@@ -65,6 +65,11 @@ Miscellaneous improvements
* implemented :py:obj:`Axis.min()` and :py:obj:`Axis.max()` methods (closes :issue:`874`).
+* implemented the :py:obj:`Range.add_plot()` method that allows to create graphs when using
+ `open_excel()` (closes :issue:`900`).
+
+* implemented the :py:obj:`Range.add_table()` method.
+
Fixes
^^^^^
diff --git a/doc/source/tutorial/tutorial_IO.ipyml b/doc/source/tutorial/tutorial_IO.ipyml
index 9a56bbc42..a57fb6717 100644
--- a/doc/source/tutorial/tutorial_IO.ipyml
+++ b/doc/source/tutorial/tutorial_IO.ipyml
@@ -319,6 +319,8 @@ cells:
sh = wb['population_births_deaths']
# dump the array population in sheet 'population_births_deaths' starting at cell A2
sh['A2'] = population.dump()
+ # or equivalently
+ sh['A2'].add_table(population)
# add 'births' in cell A10
sh['A10'] = 'births'
# dump the array births in sheet 'population_births_deaths' starting at cell A11
@@ -334,6 +336,17 @@ cells:
```
+- markdown: |
+
+ **Note:** The two syntaxes:
+
+ - sheet['cell'] = array.dump()
+ - sheet['cell'].add_table(array)
+
+ are equivalent.
+
+
+
- markdown: |
## Exporting data without headers (Excel)
@@ -541,7 +554,7 @@ cells:
metadata:
celltoolbar: Edit Metadata
kernelspec:
- display_name: Python 3
+ display_name: Python 3 (ipykernel)
language: python
name: python3
language_info:
@@ -553,7 +566,7 @@ metadata:
name: python
nbconvert_exporter: python
pygments_lexer: ipython3
- version: 3.7.3
+ version: 3.9.5
livereveal:
autolaunch: false
scroll: true
diff --git a/doc/source/tutorial/tutorial_IO.ipynb b/doc/source/tutorial/tutorial_IO.ipynb
index 6026cbcea..ae242fe4a 100644
--- a/doc/source/tutorial/tutorial_IO.ipynb
+++ b/doc/source/tutorial/tutorial_IO.ipynb
@@ -465,6 +465,8 @@
" sh = wb['population_births_deaths']\n",
" # dump the array population in sheet 'population_births_deaths' starting at cell A2\n",
" sh['A2'] = population.dump()\n",
+ " # or equivalently\n",
+ " sh['A2'].add_table(population)\n",
" # add 'births' in cell A10\n",
" sh['A10'] = 'births'\n",
" # dump the array births in sheet 'population_births_deaths' starting at cell A11 \n",
@@ -480,6 +482,20 @@
"```"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ " **Note:** The two syntaxes:\n",
+ "
\n",
+ " - sheet['cell'] = array.dump()
\n",
+ " - sheet['cell'].add_table(array)
\n",
+ "
\n",
+ " are equivalent.\n",
+ "
"
+ ]
+ },
{
"cell_type": "markdown",
"metadata": {},
@@ -774,7 +790,7 @@
"metadata": {
"celltoolbar": "Edit Metadata",
"kernelspec": {
- "display_name": "Python 3",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@@ -788,7 +804,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.7.3"
+ "version": "3.9.5"
},
"livereveal": {
"autolaunch": false,
diff --git a/doc/source/tutorial/tutorial_plotting.ipyml b/doc/source/tutorial/tutorial_plotting.ipyml
index 7dc75ba6b..5288a328a 100644
--- a/doc/source/tutorial/tutorial_plotting.ipyml
+++ b/doc/source/tutorial/tutorial_plotting.ipyml
@@ -32,6 +32,10 @@ cells:
%matplotlib inline
+- markdown: |
+ ## Python Plot (matplotlib)
+
+
- markdown: |
In a Python script, add the following import on top of the script:
@@ -145,12 +149,50 @@ cells:
See [pyplot tutorial](https://matplotlib.org/tutorials/introductory/pyplot.html) for a short introduction to `matplotlib.pyplot`.
+- markdown: |
+ ## Excel Plot
+
+
+- markdown: |
+ It is possible to dump arrays and to make plots in the same time in an Excel workbook:
+
+
+- markdown: |
+ ```python
+ # to create a new Excel file, argument overwrite_file must be set to True
+ with open_excel('workbook_with_plots.xlsx', overwrite_file=True) as wb:
+ # ---- dump data
+ # add a new sheet 'BFG' and dump the data for Belgium in it
+ wb['BFG'] = population['Belgium'].dump()
+ # store the BFG sheet in a local variable
+ sh = wb['BFG']
+ # dump the data for France using the equivalent method add_table()
+ sh['A18'].add_table(population['France'])
+ # dump the data for Germany
+ sh['A35'].add_table(population['Germany'])
+
+ # ---- use data to create plots
+ # basic plot anchored to cell H1 using data for Belgium
+ sh['H1'].add_plot('A1', title='Belgium population')
+ # basic plot anchored to cell H18 using data for the France for years 2013 to 2016
+ sh['H18'].add_plot('A18:E20', title='France population')
+ # plot with options anchored to cell H35 using data for Germany
+ sh["H35"].add_plot("A35", title="Germany population", width=500, height=300,
+ xticks_spacing=2, min_y=40_000_000, max_y=41_500_000)
+
+ # actually write data and plots in the Workbook
+ wb.save()
+
+ # the Workbook is automatically closed when getting out the block defined by the with statement
+ ```
+
+
# The lines below here may be deleted if you do not need them.
# ---------------------------------------------------------------------------
metadata:
celltoolbar: Edit Metadata
kernelspec:
- display_name: Python 3
+ display_name: Python 3 (ipykernel)
language: python
name: python3
language_info:
@@ -162,7 +204,7 @@ metadata:
name: python
nbconvert_exporter: python
pygments_lexer: ipython3
- version: 3.7.3
+ version: 3.9.5
livereveal:
autolaunch: false
scroll: true
diff --git a/doc/source/tutorial/tutorial_plotting.ipynb b/doc/source/tutorial/tutorial_plotting.ipynb
index fff1cbafe..65e9c5edf 100644
--- a/doc/source/tutorial/tutorial_plotting.ipynb
+++ b/doc/source/tutorial/tutorial_plotting.ipynb
@@ -59,6 +59,13 @@
"%matplotlib inline"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Python Plot (matplotlib)"
+ ]
+ },
{
"cell_type": "markdown",
"metadata": {},
@@ -246,12 +253,59 @@
"\n",
"See [pyplot tutorial](https://matplotlib.org/tutorials/introductory/pyplot.html) for a short introduction to `matplotlib.pyplot`."
]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Excel Plot"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "It is possible to dump arrays and to make plots in the same time in an Excel workbook:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "```python\n",
+ "# to create a new Excel file, argument overwrite_file must be set to True\n",
+ "with open_excel('workbook_with_plots.xlsx', overwrite_file=True) as wb:\n",
+ " # ---- dump data\n",
+ " # add a new sheet 'BFG' and dump the data for Belgium in it \n",
+ " wb['BFG'] = population['Belgium'].dump()\n",
+ " # store the BFG sheet in a local variable\n",
+ " sh = wb['BFG']\n",
+ " # dump the data for France using the equivalent method add_table()\n",
+ " sh['A18'].add_table(population['France'])\n",
+ " # dump the data for Germany\n",
+ " sh['A35'].add_table(population['Germany'])\n",
+ " \n",
+ " # ---- use data to create plots\n",
+ " # basic plot anchored to cell H1 using data for Belgium\n",
+ " sh['H1'].add_plot('A1', title='Belgium population')\n",
+ " # basic plot anchored to cell H18 using data for the France for years 2013 to 2016\n",
+ " sh['H18'].add_plot('A18:E20', title='France population')\n",
+ " # plot with options anchored to cell H35 using data for Germany\n",
+ " sh[\"H35\"].add_plot(\"A35\", title=\"Germany population\", width=500, height=300, \n",
+ " xticks_spacing=2, min_y=40_000_000, max_y=41_500_000)\n",
+ " \n",
+ " # actually write data and plots in the Workbook\n",
+ " wb.save()\n",
+ " \n",
+ "# the Workbook is automatically closed when getting out the block defined by the with statement\n",
+ "```"
+ ]
}
],
"metadata": {
"celltoolbar": "Edit Metadata",
"kernelspec": {
- "display_name": "Python 3",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@@ -265,7 +319,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.7.3"
+ "version": "3.9.5"
},
"livereveal": {
"autolaunch": false,
diff --git a/larray/inout/xw_excel.py b/larray/inout/xw_excel.py
index 6ba5e621c..b17e7737d 100644
--- a/larray/inout/xw_excel.py
+++ b/larray/inout/xw_excel.py
@@ -1,6 +1,8 @@
import os
import atexit
+from typing import Union, Dict, Callable, Any
+
import numpy as np
try:
import xlwings as xw
@@ -618,6 +620,69 @@ def load(self, header=True, convert_float=True, nb_axes=None, index_col=None, fi
else:
return Array(list_data)
+ def add_table(self, array: Array):
+ self.xw_range.value = array.dump()
+
+ def add_plot(self, data_source: str, width: int=427, height: int=230, title: str=None, template: str=None,
+ min_y: Union[int, float]=None, max_y: Union[int, float]=None,
+ xticks_spacing: Union[int, float]=None, customize_func: Callable=None,
+ customize_kwargs: Dict[str, str]=None) -> Any:
+ from xlwings.constants import LegendPosition, ChartType, RowCol, AxisType, Constants, Direction
+ if customize_func is not None and not callable(customize_func):
+ raise TypeError(f"Expected a function for the argument 'customize_func'. "
+ f"Got object of type {type(customize_func).__name__} instead.")
+ if template is not None and not os.path.isfile(template):
+ raise ValueError(f"Could not find template file {template}")
+ title = str(title) if title is not None else None
+ sheet = self.sheet.xw_sheet.api
+ data_range = sheet.Range(data_source)
+ top_left_cell = data_range.Cells(1, 1)
+ # expand if current range is one cell
+ if data_range.Count == 1:
+ bottom_left_cell = data_range.End(Direction.xlDown)
+ bottom_right_cell = bottom_left_cell.End(Direction.xlToRight)
+ data_range = sheet.Range(top_left_cell, bottom_right_cell)
+ # horrible hack to make sure that Excel will consider the first column as the xticks
+ top_left_cell_value = top_left_cell.Value
+ top_left_cell.Value = ''
+ # start chart
+ sheet_charts = sheet.ChartObjects()
+ range_chart = self.xw_range.api
+ left, top = range_chart.Left, range_chart.Top
+ obj = sheet_charts.Add(left, top, width, height)
+ obj_chart = obj.Chart
+ obj_chart.SetSourceData(data_range)
+ obj_chart.ChartType = ChartType.xlLine
+ # title
+ if title is not None:
+ obj_chart.HasTitle = True
+ obj_chart.ChartTitle.Caption = title
+ # legend
+ obj_chart.Legend.Position = LegendPosition.xlLegendPositionBottom
+ # template
+ if template is not None:
+ obj_chart.ApplyChartTemplate(template)
+ # min - max on Y axis
+ if min_y is not None:
+ obj_chart.Axes(AxisType.xlValue).MinimumScale = min_y
+ if max_y is not None:
+ obj_chart.Axes(AxisType.xlValue).MaximumScale = max_y
+ # xticks_spacing
+ if xticks_spacing is not None:
+ obj_chart.Axes(AxisType.xlCategory).TickLabelSpacing = xticks_spacing
+ obj_chart.Axes(AxisType.xlCategory).TickMarkSpacing = xticks_spacing
+ obj_chart.Axes(AxisType.xlCategory).TickLabelPosition = Constants.xlLow
+ # user's function (to apply on remaining kwargs)
+ if customize_func is not None:
+ customize_func(obj_chart, **customize_kwargs)
+ # flagflip
+ nb_xticks = data_range.Rows.Count - 1
+ nb_series = data_range.Columns.Count - 1
+ if nb_series > 1 and nb_xticks == 1:
+ obj_chart.PlotBy = RowCol.xlRows
+ # see above
+ top_left_cell.Value = top_left_cell_value
+ return obj_chart
# XXX: deprecate this function?
def open_excel(filepath=None, overwrite_file=False, visible=None, silent=None, app=None, load_addins=None):
@@ -744,25 +809,34 @@ def open_excel(filepath=None, overwrite_file=False, visible=None, silent=None, a
Examples
--------
->>> arr = ndtest((3, 3))
->>> arr
-a\b b0 b1 b2
- a0 0 1 2
- a1 3 4 5
- a2 6 7 8
+>>> arr, arr2 = ndtest((3, 3)), ndtest((2, 2))
create a new Excel file and save an array
>>> # to create a new Excel file, argument overwrite_file must be set to True
>>> with open_excel('excel_file.xlsx', overwrite_file=True) as wb: # doctest: +SKIP
+... # create a new sheet 'arr' and dump the array 'arr' starting at cell A1
... wb['arr'] = arr.dump()
+...
+... # dump array 'arr2' starting at cell F1
+... wb['arr']['F1'].add_table(arr2)
+...
+... # add a plot with left top corner anchored to cell A6 and
+... # using data in range A1:D4
+... wb['arr']['A6'].add_plot('A1:D4', title='simple graph')
+...
+... # add a plot with left top corner anchored to cell F6 and
+... # using data in range F1:H3 where H3 is deduced automatically
+... wb['arr']['F6'].add_plot('F1', title='second simple graph')
+...
+... save the workbook
... wb.save()
read array from an Excel file
>>> with open_excel('excel_file.xlsx') as wb: # doctest: +SKIP
-... arr2 = wb['arr'].load()
->>> arr2 # doctest: +SKIP
+... arr3 = wb['arr'].load()
+>>> arr3 # doctest: +SKIP
a\b b0 b1 b2
a0 0 1 2
a1 3 4 5
diff --git a/larray/tests/test_excel.py b/larray/tests/test_excel.py
index ba783032f..fc747f506 100644
--- a/larray/tests/test_excel.py
+++ b/larray/tests/test_excel.py
@@ -250,6 +250,40 @@ def test_repr(self):
1 3 4 5"""
+@needs_xlwings
+def test_add_table():
+ arr = ndtest((3, 3))
+
+ with open_excel(filepath='test_add_table.xlsx', visible=False, overwrite_file=True) as wb:
+ sheet = wb[0]
+ sheet["B2"].add_table(arr)
+ wb.save()
+
+
+@needs_xlwings
+def test_add_plot():
+ demo = load_example_data('demography_eurostat')
+ population = demo.population
+ population_be = population['Belgium']
+ population_be_nan = population_be.astype(float)
+ population_be_nan[2013] = nan
+
+ with open_excel(filepath='test_add_plot.xlsx', visible=False, overwrite_file=True) as wb:
+ wb['BFG'] = population_be.dump()
+ sheet = wb['BFG']
+ sheet["H1"].add_plot("A1", title="Belgium")
+ sheet["R1"].add_plot("A1:F4")
+
+ sheet["A18"].add_table(population['France'])
+ sheet["H18"].add_plot("A18:E20", title="France")
+
+ sheet["A35"].add_table(population["Germany"])
+ sheet["H35"].add_plot("A35", title="Germany", width=500, height=300, xticks_spacing=2,
+ min_y=40_000_000, max_y=41_500_000)
+
+ wb.save()
+
+
# ================ #
# Test ExcelReport #
# ================ #