diff --git a/condarecipe/larray/meta.yaml b/condarecipe/larray/meta.yaml index c5c83e9ce..856063fcb 100644 --- a/condarecipe/larray/meta.yaml +++ b/condarecipe/larray/meta.yaml @@ -28,6 +28,11 @@ requirements: test: requires: - pytest + - pytables + - matplotlib-base + - openpyxl + - xlsxwriter + - pydantic >=2 imports: - larray diff --git a/larray/core/array.py b/larray/core/array.py index 32975d31b..347a67ec2 100644 --- a/larray/core/array.py +++ b/larray/core/array.py @@ -7330,7 +7330,7 @@ def plot(self) -> PlotObject: Line plot with grid and a title, saved in a file - >>> arr.plot(grid=True, title='line plot', filepath='my_file.png') + >>> arr.plot(grid=True, title='line plot', filepath='my_file.png') # doctest: +SKIP 2 bar plots (one for each gender) sharing the same y axis, which makes sub plots easier to compare. By default sub plots are independant of each other and the axes @@ -7352,7 +7352,7 @@ def plot(self) -> PlotObject: Create a figure containing 2 x 2 graphs - >>> import matplotlib.pyplot as plt + >>> import matplotlib.pyplot as plt # doctest: +SKIP >>> # see matplotlib.pyplot.subplots documentation for more details >>> fig, ax = plt.subplots(2, 2, figsize=(10, 8), tight_layout=True) # doctest: +SKIP >>> # line plot with 2 curves (Males and Females) in the top left corner (0, 0) diff --git a/larray/tests/common.py b/larray/tests/common.py index a2ba248cf..4b0dfb196 100644 --- a/larray/tests/common.py +++ b/larray/tests/common.py @@ -8,6 +8,11 @@ import numpy as np from numpy.lib import NumpyVersion import pandas as pd + +try: + import matplotlib +except ImportError: + matplotlib = None try: import xlwings as xw except ImportError: @@ -141,11 +146,26 @@ def meta(): score=9.70, date=pd.Timestamp(dt.datetime(1970, 3, 21))) -needs_pytables = pytest.mark.skipif(tables is None, reason="pytables is required for this test") -needs_xlwings = pytest.mark.skipif(SKIP_EXCEL_TESTS or xw is None, reason="xlwings is required for this test") -needs_openpyxl = pytest.mark.skipif(SKIP_EXCEL_TESTS or openpyxl is None, reason="openpyxl is required for this test") -needs_xlsxwriter = pytest.mark.skipif(SKIP_EXCEL_TESTS or xlsxwriter is None, - reason="xlsxwriter is required for this test") +needs_matplotlib = ( + pytest.mark.skipif(matplotlib is None, + reason="matplotlib is required for this test") +) +needs_pytables = ( + pytest.mark.skipif(tables is None, + reason="pytables is required for this test") +) +needs_xlwings = ( + pytest.mark.skipif(SKIP_EXCEL_TESTS or xw is None, + reason="xlwings is required for this test") +) +needs_openpyxl = ( + pytest.mark.skipif(SKIP_EXCEL_TESTS or openpyxl is None, + reason="openpyxl is required for this test") +) +needs_xlsxwriter = ( + pytest.mark.skipif(SKIP_EXCEL_TESTS or xlsxwriter is None, + reason="xlsxwriter is required for this test") +) @contextmanager diff --git a/larray/tests/test_array.py b/larray/tests/test_array.py index be2118978..32bf274a9 100644 --- a/larray/tests/test_array.py +++ b/larray/tests/test_array.py @@ -1,24 +1,28 @@ import os import sys +from io import StringIO import pytest import numpy as np import pandas as pd -import matplotlib.figure - -from io import StringIO -from larray.tests.common import (meta, inputpath, - assert_larray_equal, assert_larray_nan_equal, assert_larray_equiv, - assert_nparray_equal, assert_nparray_nan_equal, - needs_xlwings, needs_pytables, needs_xlsxwriter, needs_openpyxl, NUMPY2, - must_warn, must_raise) -from larray import (Array, LArray, Axis, AxisCollection, LGroup, IGroup, Metadata, - zeros, zeros_like, ndtest, empty, ones, full, eye, diag, stack, sequence, - asarray, union, clip, exp, where, X, mean, inf, nan, isnan, round, - read_hdf, read_csv, read_eurostat, read_excel, open_excel, - from_lists, from_string, from_frame, from_series, - zip_array_values, zip_array_items, nan_to_num) +from larray.tests.common import ( + meta, inputpath, + assert_larray_equal, assert_larray_nan_equal, assert_larray_equiv, + assert_nparray_equal, assert_nparray_nan_equal, + needs_xlwings, needs_pytables, needs_xlsxwriter, needs_openpyxl, + needs_matplotlib, + NUMPY2, + must_warn, must_raise +) +from larray import ( + Array, LArray, Axis, AxisCollection, LGroup, IGroup, Metadata, + zeros, zeros_like, ndtest, empty, ones, full, eye, diag, stack, sequence, + asarray, union, clip, exp, where, X, mean, inf, nan, isnan, round, + read_hdf, read_csv, read_eurostat, read_excel, open_excel, + from_lists, from_string, from_frame, from_series, + zip_array_values, zip_array_items, nan_to_num +) from larray.core.axis import ( _to_ticks, _to_key, _retarget_warn_msg, _group_as_aggregated_label_msg ) @@ -5568,7 +5572,9 @@ def test_broadcast_with(): assert_larray_equiv(b, a2) +@needs_matplotlib def test_plot(): + import matplotlib.figure import matplotlib.pyplot as plt fig, ax = plt.subplots() # doctest: +SKIP diff --git a/larray/util/plot.py b/larray/util/plot.py index 5c29aa568..c044b851c 100644 --- a/larray/util/plot.py +++ b/larray/util/plot.py @@ -1,37 +1,40 @@ import numpy as np -from matplotlib.ticker import MaxNLocator +try: + from matplotlib.ticker import MaxNLocator -class MaxNMultipleWithOffsetLocator(MaxNLocator): - def __init__(self, nbins=None, offset=0.5, **kwargs): - super().__init__(nbins, **kwargs) - self.offset = offset + class MaxNMultipleWithOffsetLocator(MaxNLocator): + def __init__(self, nbins=None, offset=0.5, **kwargs): + super().__init__(nbins, **kwargs) + self.offset = offset - def tick_values(self, vmin, vmax): - # matplotlib calls them vmin and vmax but they are actually the limits and vmin can be > vmax - invert = vmin > vmax - if invert: - vmin, vmax = vmax, vmin + def tick_values(self, vmin, vmax): + # matplotlib calls them vmin and vmax but they are actually the limits and vmin can be > vmax + invert = vmin > vmax + if invert: + vmin, vmax = vmax, vmin - max_desired_ticks = self._nbins - # not + 1 because we place ticks in the middle - num_ticks = vmax - vmin - desired_numticks = min(num_ticks, max_desired_ticks) - if desired_numticks < num_ticks: - step = np.ceil(num_ticks / desired_numticks) - else: - step = 1 - vmin = int(vmin) - vmax = int(vmax) - # when we have an offset, we do not add 1 to vmax because we place ticks in the middle - # (by adding the offset), and would result in the last "tick" being outside the limits - stop = vmax + 1 if self.offset == 0 else vmax - new_ticks = np.arange(vmin, stop, step) - if invert: - new_ticks = new_ticks[::-1] - return new_ticks + self.offset + max_desired_ticks = self._nbins + # not + 1 because we place ticks in the middle + num_ticks = vmax - vmin + desired_numticks = min(num_ticks, max_desired_ticks) + if desired_numticks < num_ticks: + step = np.ceil(num_ticks / desired_numticks) + else: + step = 1 + vmin = int(vmin) + vmax = int(vmax) + # when we have an offset, we do not add 1 to vmax because we place ticks in the middle + # (by adding the offset), and would result in the last "tick" being outside the limits + stop = vmax + 1 if self.offset == 0 else vmax + new_ticks = np.arange(vmin, stop, step) + if invert: + new_ticks = new_ticks[::-1] + return new_ticks + self.offset - def __call__(self): - """Return the locations of the ticks.""" - vmin, vmax = self.axis.get_view_interval() - return self.tick_values(vmin, vmax) + def __call__(self): + """Return the locations of the ticks.""" + vmin, vmax = self.axis.get_view_interval() + return self.tick_values(vmin, vmax) +except ImportError: + pass \ No newline at end of file