Repository: biolab/orange3

Issue:
Violin Plot: add grids for easier interpretation
<!-- 
Thanks for taking the time to submit a feature request!
For the best chance at our team considering your request, please answer the following questions to the best of your ability.
-->

**What's your use case?**
<!-- In other words, what's your pain point? -->
<!-- Is your request related to a problem, or perhaps a frustration? -->
<!-- Tell us the story that led you to write this request. -->
Looking at Violin Plot it is sometimes hard to know precisely the min and the max of the data points.




**What's your proposed solution?**
<!-- Be specific, clear, and concise. -->
Perhaps we could add a grid that would simplify interpretation of the results? Similar to the grid in Scatter Plot.




**Are there any alternative solutions?**
Hover on data points.







Discussion:

Resolving patch:
diff --git a/Orange/widgets/visualize/owviolinplot.py b/Orange/widgets/visualize/owviolinplot.py
--- a/Orange/widgets/visualize/owviolinplot.py
+++ b/Orange/widgets/visualize/owviolinplot.py
@@ -376,7 +376,8 @@ class ViolinPlot(PlotWidget):
 
     def __init__(self, parent: OWWidget, kernel: str, scale: int,
                  orientation: Qt.Orientations, show_box_plot: bool,
-                 show_strip_plot: bool, show_rug_plot: bool, sort_items: bool):
+                 show_strip_plot: bool, show_rug_plot: bool, show_grid: bool,
+                 sort_items: bool):
 
         # data
         self.__values: Optional[np.ndarray] = None
@@ -391,6 +392,7 @@ def __init__(self, parent: OWWidget, kernel: str, scale: int,
         self.__show_box_plot = show_box_plot
         self.__show_strip_plot = show_strip_plot
         self.__show_rug_plot = show_rug_plot
+        self.__show_grid = show_grid
         self.__sort_items = sort_items
 
         # items
@@ -486,6 +488,11 @@ def set_show_rug_plot(self, show: bool):
             for item in self.__violin_items:
                 item.set_show_rug_plot(show)
 
+    def set_show_grid(self, show: bool):
+        if self.__show_grid != show:
+            self.__show_grid = show
+            self._update_grid()
+
     def set_sort_items(self, sort_items: bool):
         if self.__sort_items != sort_items:
             self.__sort_items = sort_items
@@ -578,6 +585,11 @@ def _plot_data(self):
 
         # apply selection ranges
         self._selection_ranges = ranges
+        self._update_grid()
+
+    def _update_grid(self):
+        self.showGrid(x=self.__show_grid and self.__orientation == Qt.Horizontal,
+                      y=self.__show_grid and self.__orientation == Qt.Vertical)
 
     def _set_violin_item(self, values: np.ndarray, color: QColor):
         values = values[~np.isnan(values)]
@@ -639,8 +651,8 @@ def _clear_data_items(self):
         self.__selection_rects.clear()
 
     def _clear_axes(self):
-        self.setAxisItems({"bottom": AxisItem(orientation="bottom"),
-                           "left": AxisItem(orientation="left")})
+        self.getAxis("left").setTicks(None)
+        self.getAxis("bottom").setTicks(None)
         Updater.update_axes_titles_font(
             self.parameter_setter.axis_items,
             **self.parameter_setter.titles_settings
@@ -743,6 +755,7 @@ class Error(OWWidget.Error):
     show_box_plot = Setting(True)
     show_strip_plot = Setting(False)
     show_rug_plot = Setting(False)
+    show_grid = Setting(False)
     order_violins = Setting(False)
     orientation_index = Setting(1)  # Vertical
     kernel_index = Setting(0)  # Normal kernel
@@ -781,7 +794,8 @@ def _add_graph(self):
         self.graph = ViolinPlot(self, self.kernel,
                                 self.scale_index, self.orientation,
                                 self.show_box_plot, self.show_strip_plot,
-                                self.show_rug_plot, self.order_violins)
+                                self.show_rug_plot, self.show_grid,
+                                self.order_violins)
         self.graph.selection_changed.connect(self.__selection_changed)
         box.layout().addWidget(self.graph)
 
@@ -844,6 +858,10 @@ def _add_controls(self):
             box, self, "order_violins", "Order subgroups",
             callback=self.__order_violins_changed,
         )
+        gui.checkBox(
+            box, self, "show_grid", "Show grid",
+            callback=self.__show_grid_changed,
+        )
         gui.radioButtons(box, self, "orientation_index",
                          ["Horizontal", "Vertical"], label="Orientation: ",
                          orientation=Qt.Horizontal,
@@ -889,6 +907,9 @@ def __show_rug_plot_changed(self):
     def __order_violins_changed(self):
         self.graph.set_sort_items(self.order_violins)
 
+    def __show_grid_changed(self):
+        self.graph.set_show_grid(self.show_grid)
+
     def __orientation_changed(self):
         self.graph.set_orientation(self.orientation)
 


Test patch:
diff --git a/Orange/widgets/visualize/tests/test_owviolinplot.py b/Orange/widgets/visualize/tests/test_owviolinplot.py
--- a/Orange/widgets/visualize/tests/test_owviolinplot.py
+++ b/Orange/widgets/visualize/tests/test_owviolinplot.py
@@ -66,6 +66,7 @@ def test_controls(self):
         self.widget.controls.show_strip_plot.setChecked(True)
         self.widget.controls.show_rug_plot.setChecked(True)
         self.widget.controls.order_violins.setChecked(True)
+        self.widget.controls.show_grid.setChecked(True)
         self.widget.controls.orientation_index.buttons[0].click()
         self.widget.controls.kernel_index.setCurrentIndex(1)
         self.widget.controls.scale_index.setCurrentIndex(1)
@@ -75,6 +76,7 @@ def test_controls(self):
         self.widget.controls.show_strip_plot.setChecked(False)
         self.widget.controls.show_rug_plot.setChecked(False)
         self.widget.controls.order_violins.setChecked(False)
+        self.widget.controls.show_grid.setChecked(False)
         self.widget.controls.orientation_index.buttons[1].click()
         self.widget.controls.kernel_index.setCurrentIndex(0)
         self.widget.controls.scale_index.setCurrentIndex(2)
@@ -84,6 +86,7 @@ def test_controls(self):
         self.widget.controls.show_strip_plot.setChecked(True)
         self.widget.controls.show_rug_plot.setChecked(True)
         self.widget.controls.order_violins.setChecked(True)
+        self.widget.controls.show_grid.setChecked(True)
         self.widget.controls.orientation_index.buttons[0].click()
         self.widget.controls.kernel_index.setCurrentIndex(1)
         self.widget.controls.scale_index.setCurrentIndex(1)
@@ -108,6 +111,39 @@ def test_enable_controls(self):
         self.assertTrue(self.widget.controls.order_violins.isEnabled())
         self.assertTrue(self.widget.controls.scale_index.isEnabled())
 
+    @patch("Orange.widgets.visualize.owviolinplot.ViolinPlot.set_show_grid")
+    def test_show_grid_sets_show_grid(self, show_grid):
+        self.send_signal(self.widget.Inputs.data, self.data)
+        self.widget.show_grid = True
+
+        show_grid.reset_mock()
+        self.widget.controls.show_grid.click()
+        show_grid.assert_called_once()
+
+        show_grid.reset_mock()
+        self.widget.controls.show_grid.click()
+        show_grid.assert_called_once()
+
+    def test_show_grid_orientation(self):
+        if not self.widget.show_grid:
+            self.widget.controls.show_grid.click()
+        assert self.widget.show_grid
+
+        self.send_signal(self.widget.Inputs.data, self.data)
+
+        self.widget.controls.orientation_index.buttons[1].click()  # Vertical
+        get_axis = self.widget.graph.plotItem.getAxis
+        self.assertIs(get_axis("bottom").grid, False)
+        self.assertIsNot(get_axis("left").grid, False)
+
+        self.widget.controls.orientation_index.buttons[0].click()
+        self.assertIsNot(get_axis("bottom").grid, False)
+        self.assertIs(get_axis("left").grid, False)
+
+        self.widget.controls.show_grid.click()
+        self.assertIs(get_axis("bottom").grid, False)
+        self.assertIs(get_axis("left").grid, False)
+
     def test_datasets(self):
         self.widget.controls.show_strip_plot.setChecked(True)
         self.widget.controls.show_rug_plot.setChecked(True)