Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 42 additions & 4 deletions Orange/widgets/data/owcreateclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@

import numpy as np

from AnyQt.QtWidgets import QGridLayout, QLabel, QLineEdit, QSizePolicy, QWidget
from AnyQt.QtCore import Qt
from AnyQt.QtWidgets import QFrame, QGridLayout, QLabel, QLineEdit, \
QSizePolicy, QWidget, QScrollArea
from AnyQt.QtCore import Qt, QTimer

from Orange.data import StringVariable, DiscreteVariable, Domain
from Orange.data.table import Table
Expand Down Expand Up @@ -227,6 +228,8 @@ class Outputs:

want_main_area = False
buttons_area_orientation = Qt.Vertical
#: Max pixel height of the rules area before it scrolls instead of

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is incomplete.

MAX_RULES_AREA_HEIGHT = 360

settingsHandler = DomainContextHandler()
attribute = ContextSetting(None)
Expand Down Expand Up @@ -269,6 +272,9 @@ def __init__(self):
self.remove_buttons = []
#: list of list of QLabel: pairs of labels with counts
self.counts = []
#: bool: set by add_row, tells _refit_rules_area to scroll down
# once the new row's height has been applied
self._scroll_to_bottom_pending = False

gui.lineEdit(
self.controlArea, self, "class_name",
Expand Down Expand Up @@ -301,9 +307,15 @@ def __init__(self):
rules_box.addWidget(QLabel("Count"), 0, 3, 1, 2)
self.update_rules()

widg = QWidget(patternbox)
self._rules_widget = widg = QWidget()
widg.setLayout(rules_box)
patternbox.layout().addWidget(widg)

self._rules_scroll = scroll = QScrollArea()
scroll.setWidget(widg)
scroll.setWidgetResizable(True)
scroll.setFrameShape(QFrame.NoFrame)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
patternbox.layout().addWidget(scroll)

box = gui.hBox(patternbox)
gui.rubber(box)
Expand Down Expand Up @@ -331,6 +343,8 @@ def __init__(self):
# TODO: Resizing upon changing the number of rules does not work

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the comment.

self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the extra blank lines.



@property
def active_rules(self):
"""
Expand Down Expand Up @@ -427,9 +441,33 @@ def _fix_tab_order():
_remove_line()
_fix_tab_order()

QTimer.singleShot(0, self._refit_rules_area)

def _refit_rules_area(self):
"""Size the rules scroll area to its contents, but never taller than
MAX_RULES_AREA_HEIGHT (beyond that it scrolls, so the Apply button
stays on screen), then re-fit the window to its new contents."""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the docstring. It's not needed for the protected function, besides the name of the function is representative enough.

content_height = self._rules_widget.sizeHint().height()
needs_scroll = content_height > self.MAX_RULES_AREA_HEIGHT
self._rules_scroll.setVerticalScrollBarPolicy(
Qt.ScrollBarAlwaysOn if needs_scroll else Qt.ScrollBarAlwaysOff)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is not needed. The setFixedHeight will suffice.

self._rules_scroll.setFixedHeight(
min(content_height, self.MAX_RULES_AREA_HEIGHT))
self.adjustSize()

if self._scroll_to_bottom_pending:
self._scroll_to_bottom_pending = False
QTimer.singleShot(0, self._scroll_rules_to_bottom)

def _scroll_rules_to_bottom(self):
"""Scroll the rules area down so a newly added row is visible."""
bar = self._rules_scroll.verticalScrollBar()
bar.setValue(bar.maximum())

def add_row(self):
"""Append a new row at the end."""
self.active_rules.append(["", ""])
self._scroll_to_bottom_pending = True
self.adjust_n_rule_rows()
self.update_counts()

Expand Down
33 changes: 31 additions & 2 deletions Orange/widgets/visualize/owdistributions.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,16 @@ def update_setters(self):
self.initial_settings = {
self.LABELS_BOX: {
self.FONT_FAMILY_LABEL: self.FONT_FAMILY_SETTING,
self.TITLE_LABEL: self.FONT_SETTING,
self.AXIS_TITLE_LABEL: self.FONT_SETTING,
self.AXIS_TICKS_LABEL: self.FONT_SETTING,
self.LEGEND_LABEL: self.FONT_SETTING,
},
self.ANNOT_BOX: {
self.TITLE_LABEL: {self.TITLE_LABEL: ("", "")},
self.X_AXIS_LABEL: {self.TITLE_LABEL: ("", "")},
self.Y_AXIS_LABEL: {self.TITLE_LABEL: ("", "")},
},
self.PLOT_BOX: {
self.LEGEND_LABEL: {
self.HIDE_EMPTY_LABEL: (None, False)
Expand All @@ -209,6 +215,14 @@ def update_show_empty(**_settings):
self.LEGEND_LABEL: update_show_empty,
}

@property
def title_item(self):
return self.master.parent_widget.ploti.titleLabel

@property
def getAxis(self):
return self.master.parent_widget.ploti.getAxis

@property
def axis_items(self):
return [value["item"] for value in
Expand Down Expand Up @@ -648,17 +662,27 @@ def _clear_plot(self):
def _set_axis_names(self):
assert self.is_valid # called only from replot, so assumes data is OK
bottomaxis = self.ploti.getAxis("bottom")
bottomaxis.setLabel(self.var and self.var.name)
bottomaxis.setLabel(
self._custom_axis_title(ParameterSetter.X_AXIS_LABEL)
or (self.var and self.var.name))
bottomaxis.setShowUnit(not (self.var and self.var.is_time))

leftaxis = self.ploti.getAxis("left")
if self.show_probs and self.cvar:
custom_label = self._custom_axis_title(ParameterSetter.Y_AXIS_LABEL)
if custom_label:
leftaxis.setLabel(custom_label)
elif self.show_probs and self.cvar:
leftaxis.setLabel(
f"Probability of '{self.cvar.name}' at given '{self.var.name}'")
else:
leftaxis.setLabel("Frequency")
leftaxis.resizeEvent()

def _custom_axis_title(self, axis_label):
return self.visual_settings.get(
(ParameterSetter.ANNOT_BOX, axis_label, ParameterSetter.TITLE_LABEL),
"")

def _update_controls_state(self):
assert self.is_valid # called only from replot, so assumes data is OK
self.controls.sort_by_freq.setDisabled(self.var.is_continuous)
Expand Down Expand Up @@ -1391,6 +1415,11 @@ def send_report(self):
def set_visual_settings(self, key: KeyType, value: ValueType):
self.visual_settings[key] = value
self.plotview.parameter_setter.set_parameter(key, value)
if self.is_valid and key[:2] in (
(ParameterSetter.ANNOT_BOX, ParameterSetter.X_AXIS_LABEL),
(ParameterSetter.ANNOT_BOX, ParameterSetter.Y_AXIS_LABEL)):
# an empty custom title falls back to the auto-generated label
self._set_axis_names()


if __name__ == "__main__": # pragma: no cover
Expand Down
105 changes: 105 additions & 0 deletions Orange/widgets/visualize/tests/test_owdistributions.py
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,13 @@ def test_visual_settings(self):
key, value = ("Fonts", "Font family", "Font family"), "Helvetica"
self.widget.set_visual_settings(key, value)

key, value = ("Fonts", "Title", "Font size"), 20
self.widget.set_visual_settings(key, value)
key, value = ("Fonts", "Title", "Italic"), True
self.widget.set_visual_settings(key, value)
font.setPointSize(20)
self.assertFontEqual(graph.parameter_setter.title_item.item.font(), font)

key, value = ("Fonts", "Axis title", "Font size"), 16
self.widget.set_visual_settings(key, value)
key, value = ("Fonts", "Axis title", "Italic"), True
Expand Down Expand Up @@ -785,6 +792,104 @@ def test_visual_settings(self):
["Iris-versicolor", "Iris-virginica"]
)

self.assertFalse(graph.parameter_setter.title_item.isVisible())
key, value = ("Annotations", "Title", "Title"), "iris distributions"
self.widget.set_visual_settings(key, value)
self.assertTrue(graph.parameter_setter.title_item.isVisible())
self.assertEqual(graph.parameter_setter.title_item.item.toPlainText(), "iris distributions")

key, value = ("Annotations", "x-axis title", "Title"), "x-axis custom label"
self.widget.set_visual_settings(key, value)
self.assertEqual(self.widget.ploti.getAxis("bottom").labelText, "x-axis custom label")

key, value = ("Annotations", "y-axis title", "Title"), "y-axis custom label"
self.widget.set_visual_settings(key, value)
self.assertEqual(self.widget.ploti.getAxis("left").labelText, "y-axis custom label")

def test_custom_titles_variable_change(self):
"""Custom titles persist when plotted variable is changed"""
self.send_signal(self.widget.Inputs.data, self.iris)
graph = self.widget.plotview

key, value = ("Annotations", "Title", "Title"), "iris distributions"
self.widget.set_visual_settings(key, value)
key, value = ("Annotations", "x-axis title", "Title"), "x-axis custom label"
self.widget.set_visual_settings(key, value)
key, value = ("Annotations", "y-axis title", "Title"), "y-axis custom label"
self.widget.set_visual_settings(key, value)

self._set_var(1)

self.assertEqual(graph.parameter_setter.title_item.item.toPlainText(), "iris distributions")
self.assertEqual(self.widget.ploti.getAxis("bottom").labelText, "x-axis custom label")
self.assertEqual(self.widget.ploti.getAxis("left").labelText, "y-axis custom label")

key, value = ("Annotations", "x-axis title", "Title"), ""
self.widget.set_visual_settings(key, value)
self.assertEqual(self.widget.ploti.getAxis("bottom").labelText, "sepal length")

key, value = ("Annotations", "y-axis title", "Title"), ""
self.widget.set_visual_settings(key, value)
self.assertEqual(self.widget.ploti.getAxis("left").labelText, "Frequency")

def test_saved_workflow(self):
"""Visual settings are saved and restored"""
font = QFont()
font.setItalic(True)
font.setFamily("Helvetica")

key, value = ("Fonts", "Title", "Font size"), 20
self.widget.set_visual_settings(key, value)
key, value = ("Fonts", "Title", "Italic"), True
self.widget.set_visual_settings(key, value)
key, value = ("Fonts", "Axis title", "Font size"), 16
self.widget.set_visual_settings(key, value)
key, value = ("Fonts", "Axis title", "Italic"), True
self.widget.set_visual_settings(key, value)
key, value = ("Fonts", "Axis ticks", "Font size"), 15
self.widget.set_visual_settings(key, value)
key, value = ("Fonts", "Axis ticks", "Italic"), True
self.widget.set_visual_settings(key, value)
key, value = ("Fonts", "Legend", "Font size"), 14
self.widget.set_visual_settings(key, value)
key, value = ("Fonts", "Legend", "Italic"), True
self.widget.set_visual_settings(key, value)
key, value = ("Figure", "Legend", "Hide empty categories in the legend"), True
self.widget.set_visual_settings(key, value)
key, value = ("Annotations", "Title", "Title"), "iris distributions"
self.widget.set_visual_settings(key, value)
key, value = ("Annotations", "x-axis title", "Title"), "x-axis custom label"
self.widget.set_visual_settings(key, value)
key, value = ("Annotations", "y-axis title", "Title"), "y-axis custom label"
self.widget.set_visual_settings(key, value)

settings = self.widget.settingsHandler.pack_data(self.widget)
w = self.create_widget(OWDistributions, stored_settings=settings)

self.send_signal(w.Inputs.data, self.iris[50:], widget=w)
key, value = ("Fonts", "Font family", "Font family"), "Helvetica"
w.set_visual_settings(key, value)

font.setPointSize(20)
self.assertFontEqual(w.plotview.parameter_setter.title_item.item.font(), font)
font.setPointSize(16)
for item in w.plotview.parameter_setter.axis_items:
self.assertFontEqual(item.label.font(), font)
font.setPointSize(15)
for item in w.plotview.parameter_setter.axis_items:
self.assertFontEqual(item.style["tickFont"], font)
font.setPointSize(14)
legend_item = list(w.plotview.parameter_setter.legend_items)[0]
self.assertFontEqual(legend_item[1].item.font(), font)
self.assertEqual(
[i[1].text for i in w.plotview.parameter_setter.legend_items],
["Iris-versicolor", "Iris-virginica"]
)
self.assertTrue(w.plotview.parameter_setter.title_item.isVisible())
self.assertEqual(w.plotview.parameter_setter.title_item.item.toPlainText(), "iris distributions")
self.assertEqual(w.ploti.getAxis("bottom").labelText, "x-axis custom label")
self.assertEqual(w.ploti.getAxis("left").labelText, "y-axis custom label")

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