diff --git a/CHANGES b/CHANGES index 3d68c9af55..4fa8076ea8 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,8 @@ Release 1.5 (unreleased) Features added -------------- +* Scatter plots can now be produced using `iris.plot.scatter` and + `iris.quickplot.scatter`. * The functions `iris.plot.plot` and `iris.quickplot.plot` now take up to two arguments, which may be cubes or coordinates, allowing the user to have full control over what is plotted on each axis. The coords keyword argument is now diff --git a/docs/iris/src/whatsnew/1.5.rst b/docs/iris/src/whatsnew/1.5.rst index d2c0df1d8d..ede09f9fa7 100644 --- a/docs/iris/src/whatsnew/1.5.rst +++ b/docs/iris/src/whatsnew/1.5.rst @@ -11,6 +11,8 @@ Iris 1.5 features A summary of the main features added with version 1.5: +* Scatter plots can now be produced using :func:`iris.plot.scatter` and + :func:`iris.quickplot.scatter`. * The functions :func:`iris.plot.plot` and :func:`iris.quickplot.plot` now take up to two arguments, which may be cubes or coordinates, allowing the user to have full control over what is plotted on each axis. The coords keyword argument diff --git a/lib/iris/plot.py b/lib/iris/plot.py index 0939a17ea1..d2b8149b01 100644 --- a/lib/iris/plot.py +++ b/lib/iris/plot.py @@ -857,6 +857,32 @@ def plot(*args, **kwargs): return _draw_1d_from_points('plot', _plot_args, *args, **kwargs) +def scatter(x, y, *args, **kwargs): + """ + Draws a scatter plot based on the given cube(s) or coordinate(s). + + Args: + + * x: :class:`~iris.cube.Cube` or :class:`~iris.coords.Coord` + A cube or a coordinate to plot on the x-axis. + + * y: :class:`~iris.cube.Cube` or :class:`~iris.coords.Coord` + A cube or a coordinate to plot on the y-axis. + + See :func:`matplotlib.pyplot.scatter` for details of valid keyword + arguments. + + """ + # here we are more specific about argument types than generic 1d plotting + if not isinstance(x, (iris.cube.Cube, iris.coords.Coord)): + raise TypeError('x must be a cube or a coordinate.') + if not isinstance(y, (iris.cube.Cube, iris.coords.Coord)): + raise TypeError('y must be a cube or a coordinate.') + args = (x, y) + args + _plot_args = None + return _draw_1d_from_points('scatter', _plot_args, *args, **kwargs) + + # Provide convenience show method from pyplot show = plt.show diff --git a/lib/iris/quickplot.py b/lib/iris/quickplot.py index aab4226661..00711a1b4e 100644 --- a/lib/iris/quickplot.py +++ b/lib/iris/quickplot.py @@ -237,5 +237,19 @@ def plot(*args, **kwargs): return result +def scatter(x, y, *args, **kwargs): + """ + Draws a labelled scatter plot based on the given cubes or + coordinates. + + See :func:`iris.plot.scatter` for details of valid arguments and + keyword arguments. + + """ + result = iplt.scatter(x, y, *args, **kwargs) + _label_1d_plot(x, y) + return result + + # Provide a convenience show method from pyplot. show = plt.show diff --git a/lib/iris/tests/results/visual_tests/test_plot.Test1dQuickplotScatter.test_coord_coord.0.png b/lib/iris/tests/results/visual_tests/test_plot.Test1dQuickplotScatter.test_coord_coord.0.png new file mode 100644 index 0000000000..4865511482 Binary files /dev/null and b/lib/iris/tests/results/visual_tests/test_plot.Test1dQuickplotScatter.test_coord_coord.0.png differ diff --git a/lib/iris/tests/results/visual_tests/test_plot.Test1dQuickplotScatter.test_coord_coord_map.0.png b/lib/iris/tests/results/visual_tests/test_plot.Test1dQuickplotScatter.test_coord_coord_map.0.png new file mode 100644 index 0000000000..51739f5cb9 Binary files /dev/null and b/lib/iris/tests/results/visual_tests/test_plot.Test1dQuickplotScatter.test_coord_coord_map.0.png differ diff --git a/lib/iris/tests/results/visual_tests/test_plot.Test1dQuickplotScatter.test_coord_cube.0.png b/lib/iris/tests/results/visual_tests/test_plot.Test1dQuickplotScatter.test_coord_cube.0.png new file mode 100644 index 0000000000..ed113878dd Binary files /dev/null and b/lib/iris/tests/results/visual_tests/test_plot.Test1dQuickplotScatter.test_coord_cube.0.png differ diff --git a/lib/iris/tests/results/visual_tests/test_plot.Test1dQuickplotScatter.test_cube_coord.0.png b/lib/iris/tests/results/visual_tests/test_plot.Test1dQuickplotScatter.test_cube_coord.0.png new file mode 100644 index 0000000000..4da8d2cfaa Binary files /dev/null and b/lib/iris/tests/results/visual_tests/test_plot.Test1dQuickplotScatter.test_cube_coord.0.png differ diff --git a/lib/iris/tests/results/visual_tests/test_plot.Test1dQuickplotScatter.test_cube_cube.0.png b/lib/iris/tests/results/visual_tests/test_plot.Test1dQuickplotScatter.test_cube_cube.0.png new file mode 100644 index 0000000000..092d6101c5 Binary files /dev/null and b/lib/iris/tests/results/visual_tests/test_plot.Test1dQuickplotScatter.test_cube_cube.0.png differ diff --git a/lib/iris/tests/results/visual_tests/test_plot.Test1dScatter.test_coord_coord.0.png b/lib/iris/tests/results/visual_tests/test_plot.Test1dScatter.test_coord_coord.0.png new file mode 100644 index 0000000000..c64f6e0819 Binary files /dev/null and b/lib/iris/tests/results/visual_tests/test_plot.Test1dScatter.test_coord_coord.0.png differ diff --git a/lib/iris/tests/results/visual_tests/test_plot.Test1dScatter.test_coord_coord_map.0.png b/lib/iris/tests/results/visual_tests/test_plot.Test1dScatter.test_coord_coord_map.0.png new file mode 100644 index 0000000000..51739f5cb9 Binary files /dev/null and b/lib/iris/tests/results/visual_tests/test_plot.Test1dScatter.test_coord_coord_map.0.png differ diff --git a/lib/iris/tests/results/visual_tests/test_plot.Test1dScatter.test_coord_cube.0.png b/lib/iris/tests/results/visual_tests/test_plot.Test1dScatter.test_coord_cube.0.png new file mode 100644 index 0000000000..3c78f20246 Binary files /dev/null and b/lib/iris/tests/results/visual_tests/test_plot.Test1dScatter.test_coord_cube.0.png differ diff --git a/lib/iris/tests/results/visual_tests/test_plot.Test1dScatter.test_cube_coord.0.png b/lib/iris/tests/results/visual_tests/test_plot.Test1dScatter.test_cube_coord.0.png new file mode 100644 index 0000000000..9156ad51c3 Binary files /dev/null and b/lib/iris/tests/results/visual_tests/test_plot.Test1dScatter.test_cube_coord.0.png differ diff --git a/lib/iris/tests/results/visual_tests/test_plot.Test1dScatter.test_cube_cube.0.png b/lib/iris/tests/results/visual_tests/test_plot.Test1dScatter.test_cube_cube.0.png new file mode 100644 index 0000000000..d0741e99fd Binary files /dev/null and b/lib/iris/tests/results/visual_tests/test_plot.Test1dScatter.test_cube_cube.0.png differ diff --git a/lib/iris/tests/test_plot.py b/lib/iris/tests/test_plot.py index 789b06dc83..69a05ba683 100644 --- a/lib/iris/tests/test_plot.py +++ b/lib/iris/tests/test_plot.py @@ -245,6 +245,85 @@ def setUp(self): self.draw_method = qplt.plot +@tests.skip_data +class Test1dScatter(tests.GraphicsTest): + + def setUp(self): + self.cube = iris.load_cube( + tests.get_data_path(('NAME', 'NAMEIII_trajectory.txt')), + 'Temperature') + self.draw_method = iplt.scatter + + def test_coord_coord(self): + x = self.cube.coord('longitude') + y = self.cube.coord('height') + c = self.cube.data + self.draw_method(x, y, c=c, edgecolor='none') + self.check_graphic() + + def test_coord_coord_map(self): + x = self.cube.coord('longitude') + y = self.cube.coord('latitude') + c = self.cube.data + self.draw_method(x, y, c=c, edgecolor='none') + plt.gca().coastlines() + self.check_graphic() + + def test_coord_cube(self): + x = self.cube.coord('latitude') + y = self.cube + c = self.cube.coord('Travel Time').points + self.draw_method(x, y, c=c, edgecolor='none') + self.check_graphic() + + def test_cube_coord(self): + x = self.cube + y = self.cube.coord('height') + c = self.cube.coord('Travel Time').points + self.draw_method(x, y, c=c, edgecolor='none') + self.check_graphic() + + def test_cube_cube(self): + x = iris.load_cube( + tests.get_data_path(('NAME', 'NAMEIII_trajectory.txt')), + 'Rel Humidity') + y = self.cube + c = self.cube.coord('Travel Time').points + self.draw_method(x, y, c=c, edgecolor='none') + self.check_graphic() + + def test_incompatible_objects(self): + # cubes/coordinates of different sizes cannot be plotted + x = self.cube + y = self.cube.coord('height')[:-1] + with self.assertRaises(ValueError): + self.draw_method(x, y) + + def test_multidimensional(self): + # multidimensional cubes/coordinates are not allowed + x = _load_4d_testcube()[0, :, :, 0] + y = x.coord('model_level_number') + with self.assertRaises(ValueError): + self.draw_method(x, y) + + def test_not_cube_or_coord(self): + # inputs must be cubes or coordinates + x = np.arange(self.cube.shape[0]) + y = self.cube + with self.assertRaises(TypeError): + self.draw_method(x, y) + + +@tests.skip_data +class Test1dQuickplotScatter(Test1dScatter): + + def setUp(self): + self.cube = iris.load_cube( + tests.get_data_path(('NAME', 'NAMEIII_trajectory.txt')), + 'Temperature') + self.draw_method = qplt.scatter + + # Caches _load_4d_testcube so subsequent calls are faster def cache(fn, cache={}): def inner(*args, **kwargs):