Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
5 changes: 5 additions & 0 deletions doc/source/changes/version_0_33.rst.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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
^^^^^

Expand Down
74 changes: 68 additions & 6 deletions larray/inout/xw_excel.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import os
import atexit

from typing import Union, Dict, Callable, Any

import numpy as np
try:
import xlwings as xw
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -744,18 +809,15 @@ 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
... wb['arr'] = arr.dump()
... wb['arr']['A6'].add_table()
... wb['arr']['G1'].add_plot('A1', title='simple graph')
... wb.save()

read array from an Excel file
Expand Down
26 changes: 26 additions & 0 deletions larray/tests/test_excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,32 @@ 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:
sheet = wb[0]
sheet["B2"] = population_be.dump()
sheet["B8"].add_plot("B2")
sheet["L8"].add_plot("B2:F4")
wb.save()


# ================ #
# Test ExcelReport #
# ================ #
Expand Down