diff --git a/autotest/test_plot_cross_section.py b/autotest/test_plot_cross_section.py index e5829b45f..4e523b031 100644 --- a/autotest/test_plot_cross_section.py +++ b/autotest/test_plot_cross_section.py @@ -195,6 +195,56 @@ def test_cross_section_invalid_line_representations_fail(line): flopy.plot.PlotCrossSection(modelgrid=grid, line={"line": line}) +@requires_pkg("shapely") +@pytest.mark.parametrize("view", ["bogus", "diagonal", "X", "Y"]) +def test_cross_section_invalid_view_raises_error(view): + grid = structured_square_grid(side=10) + if view in ("X", "Y"): + # view is case-insensitive, these should not raise + flopy.plot.PlotCrossSection( + modelgrid=grid, line={"line": [(0, 0), (10, 10)]}, view=view + ) + return + with pytest.raises(ValueError): + flopy.plot.PlotCrossSection( + modelgrid=grid, line={"line": [(0, 0), (10, 10)]}, view=view + ) + + +@requires_pkg("shapely") +@pytest.mark.parametrize( + "line, expected_auto_direction", + [ + # horizontal: x-span > y-span + ([(0, 5.5), (10, 5.5)], "x"), + # vertical: y-span > x-span + ([(5.5, 0), (5.5, 10)], "y"), + # diagonal, equal spans: falls to the "else" (y) branch since the + # comparison is a strict ">" + ([(0, 0), (10, 10)], "y"), + ], +) +def test_cross_section_view_forces_direction(line, expected_auto_direction): + grid = structured_square_grid(side=10) + + auto_xc = flopy.plot.PlotCrossSection(modelgrid=grid, line={"line": line}) + assert auto_xc.direction == expected_auto_direction + + x_xc = flopy.plot.PlotCrossSection(modelgrid=grid, line={"line": line}, view="x") + assert x_xc.direction == "x" + + y_xc = flopy.plot.PlotCrossSection(modelgrid=grid, line={"line": line}, view="y") + assert y_xc.direction == "y" + + +@pytest.mark.parametrize("onkey, expected_direction", [("row", "x"), ("column", "y")]) +@pytest.mark.parametrize("view", ["auto", "x", "y"]) +def test_cross_section_view_ignored_for_row_column(onkey, expected_direction, view): + grid = structured_square_grid(side=10) + xc = flopy.plot.PlotCrossSection(modelgrid=grid, line={onkey: 4}, view=view) + assert xc.direction == expected_direction + + def test_plot_limits(): xymin, xymax = 0, 1000 cellsize = 50 diff --git a/flopy/plot/crosssection.py b/flopy/plot/crosssection.py index 5ae25e518..39ae15a3f 100644 --- a/flopy/plot/crosssection.py +++ b/flopy/plot/crosssection.py @@ -52,12 +52,16 @@ class PlotCrossSection: cross-sectional width less than min_segment_length will be ignored and not included in the plot. Default is 1e-02. view : str - view can be used to force the view of the cross section when a line is provided. - "auto" is default and mimics the long term behavior of flopy, which decides - on the view by taking the maximum of the x and y direction of the cross - sectional line. "x" forces the view to be plotted from the x direction - (bottom of the unrotated grid). and "y" forces the view to be plotted from the - y-direction "left" or "right" side of the unrotated grid. + view can be used to force the view of the cross section when an + arbitrary line (line["line"]) is provided. "auto" is default and + mimics the long term behavior of flopy, which decides on the view + by taking the maximum of the x and y direction of the cross + sectional line. "x" forces the view to be plotted from the x + direction (bottom of the unrotated grid) and "y" forces the view to + be plotted from the y-direction ("left" or "right" side of the + unrotated grid). view has no effect when the cross section is + specified with line["row"] or line["column"], since the view is + already unambiguous in that case. """ def __init__( @@ -72,6 +76,8 @@ def __init__( view="auto", ): view = view.lower() + if view not in ("auto", "x", "y"): + raise ValueError(f"view must be 'auto', 'x', or 'y', got {view!r}") self.ax = ax self.geographic_coords = geographic_coords self.model = model @@ -168,10 +174,8 @@ def __init__( yp.append(v2) xp, yp = self.mg.get_local_coords(xp, yp) - if (np.max(xp) - np.min(xp) > np.max(yp) - np.min(yp)) or view not in ( - "auto", - "y", - ): + xspan_gt_yspan = np.max(xp) - np.min(xp) > np.max(yp) - np.min(yp) + if view == "x" or (view == "auto" and xspan_gt_yspan): # this is x-projection and we should buffer x by small amount idx0 = np.argmax(xp) idx1 = np.argmin(xp)