Skip to content

Commit

Permalink
Mix plot types (#35)
Browse files Browse the repository at this point in the history
* fix line interpolation bug

* minor bug fix in barh axis fit

* save mixing of plot types

* added test for multiplot

* added test for line interpolation
  • Loading branch information
CDonnerer committed Apr 9, 2021
1 parent 40062f2 commit 52860a2
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 15 deletions.
51 changes: 36 additions & 15 deletions src/shellplot/_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

@dataclass(frozen=True)
class PlotCall:
"""Class for keeping track of calls to various plot functions."""
"""Class for storing a call to a plot functions."""

func: callable
args: List
Expand Down Expand Up @@ -44,8 +44,23 @@ def fit(self, fig):
fig.y_axis.fit(np.concatenate([y for y in l_y]))

def fill_figure(self, fig):
if self._plot_calls[0].func is _plot:
"""Fill the a figure using the plot calls stored in self
Parameters
----------
fig : shellplot.figure.Figure
Returns
-------
None
"""
if all(plot_call.func is _plot for plot_call in self._plot_calls):
self.fit(fig)
else:
# if we mix plot types, we make sure that _plot function is called
# last. all other plotting funcs will Internally fit fig axes.
self._plot_calls.sort(key=lambda x: 1 if x.func is _plot else 0)
for plot_call in self._plot_calls:
plot_call(fig)

Expand Down Expand Up @@ -96,19 +111,19 @@ def _hist(fig, x, bins=10, **kwargs):
counts, bin_edges = np.histogram(x, bins)

fig.y_axis.limits = (0, max(counts))
counts_scaled = fig.y_axis.transform(counts)
fig.x_axis.fit(bin_edges)

bin = 0
counts_scaled = fig.y_axis.transform(counts)
bin_width = fig.x_axis.display_max // len(counts) - 1
display_max = (bin_width + 1) * len(counts)
fig.x_axis.scale = display_max / (fig.x_axis.limits[1] - fig.x_axis.limits[0])

bin = 0

for count in counts_scaled:
_add_vbar(fig.canvas, bin, bin_width, count)
bin += bin_width + 1

display_max = (bin_width + 1) * len(counts)
fig.x_axis.scale = display_max / (fig.x_axis.limits[1] - fig.x_axis.limits[0])


def _check_bins(bins, x_axis):
if isinstance(bins, int):
Expand All @@ -125,24 +140,23 @@ def _barh(fig, x, labels=None, **kwargs):
"""Horizontal bar plot"""

fig.x_axis.limits = (0, x.max())
x_scaled = fig.x_axis.fit_transform(x)
x_scaled = fig.x_axis.transform(x)

fig.y_axis.fit(np.arange(0, len(x) + 1, 1))
fig.y_axis.ticks = np.array(list(range(len(x)))) + 0.5

bin_width = fig.y_axis.display_max // len(x) - 1
display_max = (bin_width + 1) * len(x)
fig.y_axis.scale = display_max / (fig.y_axis.limits[1] - fig.y_axis.limits[0])

if labels is not None:
fig.y_axis.ticklabels = labels

bin = 0
bin_width = fig.y_axis.display_max // len(x) - 1

for val in x_scaled.data:
_add_hbar(fig.canvas, bin, bin_width, val)
bin += bin_width + 1

display_max = (bin_width + 1) * len(x)
fig.y_axis.scale = (display_max) / (fig.y_axis.limits[1] - fig.y_axis.limits[0])


def _boxplot(fig, x, labels=None, **kwargs):
"""Box plot"""
Expand Down Expand Up @@ -187,10 +201,17 @@ def _add_xy(canvas, idx, idy, marker=None, line=None):

def _line_interp(x, y, round_tol=0.4):
"""Interpolate for line plotting"""
x_interp = np.arange(x.min(), x.max(), 1)
y_interp = np.interp(x_interp, x, y)

x_min, x_max = x.min(), x.max()
if x_min == x_max:
y_interp = np.arange(y.min(), y.max(), 1)
x_interp = np.interp(y_interp, y, x)
else:
x_interp = np.arange(x.min(), x.max(), 1)
y_interp = np.interp(x_interp, x, y)

# Point selection is turned off for now
# (This could allow 'nicer' plotting when no good fit in the grid is found)
# is_discrete = np.isclose(
# y_interp,
# np.around(y_interp).astype(int),
Expand Down
113 changes: 113 additions & 0 deletions tests/test_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,49 @@ def test_plot_linear_line(x, expected_linear_line_plot):
assert plt_str == expected_linear_line_plot


@pytest.fixture
def expected_interp_plot():
return "\n".join(
[
"",
" 9┤ ",
" |: ÷÷÷ ",
" |: ÷ ",
" 6┤: ÷÷÷ ",
" |: ÷ ",
" |: ÷÷÷ ",
" 3┤: ÷ ",
" |: ÷÷÷ ",
" |: ÷ ",
" 0┤÷÷················ ",
" └┬-----┬-----┬-----┬",
" 0 3 6 9",
"",
]
)


@pytest.mark.parametrize(
"x, y",
[
([[0, 9], [0, 0], [0, 9]], [[0, 0], [0, 9], [0, 9]]),
],
)
def test_plot_interp(x, y, expected_interp_plot):

plt_str = plot(
x=x,
y=y,
figsize=(19, 10),
xlim=(0, 9),
ylim=(0, 9),
line=True,
marker=None,
return_type="str",
)
assert plt_str == expected_interp_plot


# -----------------------------------------------------------------------------
# Test `hist` function
# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -458,3 +501,73 @@ def expected_multi_boxplot():
def test_multi_boxplot(x, labels, expected_multi_boxplot):
plt_str = boxplot(x, labels=labels, figsize=(41, 21), return_type="str")
assert plt_str == expected_multi_boxplot


# -----------------------------------------------------------------------------
# Test plot mixing
# -----------------------------------------------------------------------------


@pytest.fixture
def expected_hist_with_line():
return "\n".join(
[
"",
"counts",
" 9┤··························· ",
" | | | ",
" | | | ",
" 6┤ | | ",
" | | | ",
" | | | ",
" 3┤ --------| | ",
" | | | | ",
" | --------| | | ",
" 0┤| | | | ",
" └┬--------┬--------┬--------┬--",
" 0 1 2 3",
"",
]
)


@pytest.mark.parametrize(
"x",
[
(
np.array(
# fmt: off
[
0,
1, 1, 1,
2, 2, 2,
3, 3, 3, 3, 3, 3,
]
)
),
(
pd.Series(
np.array(
# fmt: off
[
0,
1, 1, 1,
2, 2, 2,
3, 3, 3, 3, 3, 3,
]
)
)
),
],
)
def test_hist_with_line(x, expected_hist_with_line):
fig = figure(figsize=(30, 10), ylabel="counts")
fig.hist(x, bins=3)
fig.plot([0, 3], [9, 9], line=True, marker=None)
assert fig.draw() == expected_hist_with_line

# changing the order should yield the same result
fig = figure(figsize=(30, 10), ylabel="counts")
fig.plot([0, 3], [9, 9], line=True, marker=None)
fig.hist(x, bins=3)
assert fig.draw() == expected_hist_with_line

0 comments on commit 52860a2

Please sign in to comment.