Skip to content

Commit

Permalink
Merge pull request #5301 from VesnaT/enh_bar_plot
Browse files Browse the repository at this point in the history
[ENH] Bar Plot: Improve "Group by" visualization
  • Loading branch information
ajdapretnar authored Mar 5, 2021
2 parents 4d25f83 + 4a9e783 commit d7acfb9
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 11 deletions.
78 changes: 68 additions & 10 deletions Orange/widgets/visualize/owbarplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ def mouseClickEvent(self, ev):
class ParameterSetter(CommonParameterSetter):
GRID_LABEL, SHOW_GRID_LABEL = "Gridlines", "Show"
DEFAULT_ALPHA_GRID, DEFAULT_SHOW_GRID = 80, True
BOTTOM_AXIS_LABEL, IS_VERTICAL_LABEL = "Bottom axis", "Vertical tick text"
BOTTOM_AXIS_LABEL, GROUP_AXIS_LABEL = "Bottom axis", "Group axis"
IS_VERTICAL_LABEL = "Vertical ticks"

def __init__(self, master):
self.grid_settings: Dict = None
Expand Down Expand Up @@ -101,6 +102,9 @@ def update_setters(self):
self.BOTTOM_AXIS_LABEL: {
self.IS_VERTICAL_LABEL: (None, True),
},
self.GROUP_AXIS_LABEL: {
self.IS_VERTICAL_LABEL: (None, False),
},
},
}

Expand All @@ -113,9 +117,14 @@ def update_bottom_axis(**settings):
axis = self.master.getAxis("bottom")
axis.setRotateTicks(settings[self.IS_VERTICAL_LABEL])

def update_group_axis(**settings):
axis = self.master.group_axis
axis.setRotateTicks(settings[self.IS_VERTICAL_LABEL])

self._setters[self.PLOT_BOX] = {
self.GRID_LABEL: update_grid,
self.BOTTOM_AXIS_LABEL: update_bottom_axis,
self.GROUP_AXIS_LABEL: update_group_axis,
}

@property
Expand All @@ -132,18 +141,17 @@ def legend_items(self):
return self.master.legend.items


class BarPlotGraph(gui.OWComponent, pg.PlotWidget):
class BarPlotGraph(pg.PlotWidget):
selection_changed = Signal(list)
bar_width = 0.8
bar_width = 0.7

def __init__(self, master, parent=None):
gui.OWComponent.__init__(self, master)
self.selection = []
self.master: OWBarPlot = master
self.state: int = SELECT
self.bar_item: pg.BarGraphItem = None
pg.PlotWidget.__init__(
self, parent=parent,
super().__init__(
parent=parent,
viewBox=BarPlotViewBox(self),
background="w", enableMenu=False,
axisItems={"bottom": AxisItem(orientation="bottom",
Expand All @@ -155,6 +163,12 @@ def __init__(self, master, parent=None):
self.getPlotItem().buttonsHidden = True
self.getPlotItem().setContentsMargins(10, 0, 0, 10)
self.getViewBox().setMouseMode(pg.ViewBox.PanMode)

self.group_axis = AxisItem("bottom")
self.group_axis.hide()
self.group_axis.linkToView(self.getViewBox())
self.getPlotItem().layout.addItem(self.group_axis, 4, 1)

self.legend = self._create_legend()

self.tooltip_delegate = HelpEventDelegate(self.help_event)
Expand Down Expand Up @@ -189,6 +203,7 @@ def reset_graph(self):
self.clear()
self.update_bars()
self.update_axes()
self.update_group_lines()
self.update_legend()
self.reset_view()

Expand Down Expand Up @@ -216,13 +231,31 @@ def update_axes(self):
if self.bar_item is not None:
self.showAxis("left")
self.showAxis("bottom")
self.setLabel(axis="left", text=self.master.get_axes()[0])
self.setLabel(axis="bottom", text=self.master.get_axes()[1])
self.group_axis.show()

vals_label, group_label, annot_label = self.master.get_axes()
self.setLabel(axis="left", text=vals_label)
self.setLabel(axis="bottom", text=annot_label)
self.group_axis.setLabel(group_label)

ticks = [list(enumerate(self.master.get_labels()))]
self.getAxis('bottom').setTicks(ticks)

labels = np.array(self.master.get_group_labels())
_, indices, counts = \
np.unique(labels, return_index=True, return_counts=True)
ticks = [[(i + (c - 1) / 2, labels[i]) for i, c in
zip(indices, counts)]]
self.group_axis.setTicks(ticks)

if not group_label:
self.group_axis.hide()
elif not annot_label:
self.hideAxis("bottom")
else:
self.hideAxis("left")
self.hideAxis("bottom")
self.group_axis.hide()

def reset_view(self):
if self.bar_item is None:
Expand All @@ -248,6 +281,20 @@ def select_button_clicked(self):
def reset_button_clicked(self):
self.reset_view()

def update_group_lines(self):
if self.bar_item is None:
return

labels = np.array(self.master.get_group_labels())
if labels is None or len(labels) == 0:
return

_, indices = np.unique(labels, return_index=True)
offset = self.bar_width / 2 + (1 - self.bar_width) / 2
for index in sorted(indices)[1:]:
line = pg.InfiniteLine(pos=index - offset, angle=90)
self.addItem(line)

def select_by_rectangle(self, rect: QRectF):
if self.bar_item is None:
return
Expand Down Expand Up @@ -535,6 +582,15 @@ def get_labels(self) -> Optional[Union[List, np.ndarray]]:
return [self.annot_var.str_val(row[self.annot_var])
for row in self.grouped_data]

def get_group_labels(self) -> Optional[List]:
if not self.data:
return None
elif not self.group_var:
return []
else:
return [self.group_var.str_val(row[self.group_var])
for row in self.grouped_data]

def get_legend_data(self) -> List:
if not self.data or not self.color_var:
return []
Expand Down Expand Up @@ -579,10 +635,12 @@ def get_tooltip(self, index: int) -> str:
text = "<b>{}</b><br/><br/>{}".format(text, others)
return text

def get_axes(self) -> Optional[Tuple[str, str]]:
def get_axes(self) -> Optional[Tuple[str, str, str]]:
if not self.data:
return None
return self.selected_var.name, self.annot_var if self.annot_var else ""
return (self.selected_var.name,
self.group_var.name if self.group_var else "",
self.annot_var if self.annot_var else "")

def setup_plot(self):
self.graph.reset_graph()
Expand Down
28 changes: 27 additions & 1 deletion Orange/widgets/visualize/tests/test_owbarplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,27 @@ def test_init_attr_values(self):
self.assertEqual(controls.color_var.currentText(), "(Same color)")
self.assertEqual(controls.color_var.model().rowCount(), 1)

def test_group_axis(self):
group_axis = self.widget.graph.group_axis
annot_axis = self.widget.graph.getAxis('bottom')
controls = self.widget.controls

self.send_signal(self.widget.Inputs.data, self.data)
self.assertFalse(group_axis.isVisible())
self.assertTrue(annot_axis.isVisible())

simulate.combobox_activate_item(controls.group_var, "iris")
self.assertTrue(group_axis.isVisible())
self.assertFalse(annot_axis.isVisible())

simulate.combobox_activate_item(controls.annot_var, "iris")
self.assertTrue(group_axis.isVisible())
self.assertTrue(annot_axis.isVisible())

self.send_signal(self.widget.Inputs.data, None)
self.assertFalse(group_axis.isVisible())
self.assertFalse(annot_axis.isVisible())

def test_datasets(self):
controls = self.widget.controls
for ds in datasets.datasets():
Expand Down Expand Up @@ -353,11 +374,16 @@ def test_visual_settings(self):
self.widget.set_visual_settings(key, value)
self.assertFalse(graph.getAxis("left").grid)

key, value = ("Figure", "Bottom axis", "Vertical tick text"), False
key, value = ("Figure", "Bottom axis", "Vertical ticks"), False
self.assertTrue(graph.getAxis("bottom").style["rotateTicks"])
self.widget.set_visual_settings(key, value)
self.assertFalse(graph.getAxis("bottom").style["rotateTicks"])

key, value = ("Figure", "Group axis", "Vertical ticks"), True
self.assertFalse(graph.group_axis.style["rotateTicks"])
self.widget.set_visual_settings(key, value)
self.assertTrue(graph.group_axis.style["rotateTicks"])

def assertFontEqual(self, font1, font2):
self.assertEqual(font1.family(), font2.family())
self.assertEqual(font1.pointSize(), font2.pointSize())
Expand Down

0 comments on commit d7acfb9

Please sign in to comment.