Skip to content

Commit

Permalink
Refactoring of Sieve, Mosaic and VizRank.
Browse files Browse the repository at this point in the history
Extract OWWidget's methods for progress bars to mix-in class `Orange.widgets.utils.progressbar.ProgressBarMixin`. This simplifies `OWWidget` and also allows other dialogs (e.g. `VizRankDialog`) to use progress bars without being derived from `OWWidget`.

Move `HorizontalGridDelegate` from `OWColor` to `gui` - it was also used in the `OWFile` 's domain editor and now in VizRank.

Move `CanvasText`, `CanvasRectangle` and `ViewWithPress` from `OWMosaic` to `orange.widgets.visualize.utils` since they were used by Sieve and Mosaic (and may also be used elsewhere).

Move the common functionality of Scatter Plot's and Sieve's VizRank to more `VizRankDialog` and `VizRankDialogAttrPair` (module `orange.widgets.visualiza.utils`. The classes are also general enough to also support ranking of other visualizations in the future.

Derive `VizRankDialog` from `QDialog` (with the progress bar mix-in) instead of `OWWidget` with all its balast.

Visually improve VizRankDialog. Remove the uninformative score.

Remove the redundant dictionary `ScaleData.attribute_name_index` since it duplicates the functionality of `ScaleData.data_domain.index`.

Fix the type of argument `buttonType` and the return type in docstring of `gui.button`.

Rename `Orange.widget.utils.getHtmlCompatibleString` to `to_html`.
  • Loading branch information
janezd committed Jun 25, 2016
1 parent 0c8771c commit 816858d
Show file tree
Hide file tree
Showing 13 changed files with 736 additions and 630 deletions.
16 changes: 4 additions & 12 deletions Orange/widgets/data/owcolor.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
from PyQt4.QtCore import Qt, QAbstractTableModel
from PyQt4.QtGui import QStyledItemDelegate, QColor, QHeaderView, QFont, \
QColorDialog, QTableView, qRgb, QImage
import numpy as np
from PyQt4.QtCore import Qt, QAbstractTableModel
from PyQt4.QtGui import (
QColor, QHeaderView, QFont, QColorDialog, QTableView, qRgb, QImage)

import Orange
from Orange.widgets import widget, settings, gui
from Orange.widgets.gui import HorizontalGridDelegate
from Orange.widgets.utils.colorpalette import \
ContinuousPaletteGenerator, ColorPaletteDlg

ColorRole = next(gui.OrangeUserRole)


class HorizontalGridDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
painter.save()
painter.setPen(QColor(212, 212, 212))
painter.drawLine(option.rect.bottomLeft(), option.rect.bottomRight())
painter.restore()
QStyledItemDelegate.paint(self, painter, option, index)


# noinspection PyMethodOverriding
class ColorTableModel(QAbstractTableModel):
def __init__(self):
Expand Down
15 changes: 12 additions & 3 deletions Orange/widgets/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import Qt, pyqtSignal as Signal
from PyQt4.QtGui import QCursor, QApplication, QTableView, QHeaderView, \
QStyledItemDelegate, QSizePolicy
QStyledItemDelegate, QSizePolicy, QColor

# Some Orange widgets might expect this here
from Orange.widgets.webview import WebView as WebviewWidget # pylint: disable=unused-import
Expand Down Expand Up @@ -1074,8 +1074,8 @@ def button(widget, master, label, callback=None, width=None, height=None,
activated on pressing Return.
:type autoDefault: bool
:param buttonType: the button type (default: `QPushButton`)
:type buttonType: PyQt4.QtGui.QAbstractButton
:rtype: PyQt4.QtGui.QAbstractButton
:type buttonType: PyQt4.QtGui.QPushButton
:rtype: PyQt4.QtGui.QPushButton
"""
button = buttonType(widget)
if label:
Expand Down Expand Up @@ -3096,6 +3096,15 @@ def get_bar_brush(self, _, index):
return QtGui.QBrush(bar_brush)


class HorizontalGridDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
painter.save()
painter.setPen(QColor(212, 212, 212))
painter.drawLine(option.rect.bottomLeft(), option.rect.bottomRight())
painter.restore()
QStyledItemDelegate.paint(self, painter, option, index)


class VerticalLabel(QtGui.QLabel):
def __init__(self, text, parent=None):
super().__init__(text, parent)
Expand Down
8 changes: 6 additions & 2 deletions Orange/widgets/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,9 @@ def getdeepattr(obj, attr, *arg, **kwarg):
return kwarg["default"]
raise

def getHtmlCompatibleString(strVal):
return strVal.replace("<=", "&#8804;").replace(">=","&#8805;").replace("<", "&#60;").replace(">","&#62;").replace("=\\=", "&#8800;")

def to_html(str):
return str.replace("<=", "&#8804;").replace(">=", "&#8805;").\
replace("<", "&#60;").replace(">", "&#62;").replace("=\\=", "&#8800;")

getHtmlCompatibleString = to_html
2 changes: 1 addition & 1 deletion Orange/widgets/utils/domaineditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from Orange.data import DiscreteVariable, ContinuousVariable, StringVariable, \
TimeVariable
from Orange.widgets import gui
from Orange.widgets.data.owcolor import HorizontalGridDelegate
from Orange.widgets.gui import HorizontalGridDelegate
from Orange.widgets.utils.itemmodels import TableModel


Expand Down
159 changes: 159 additions & 0 deletions Orange/widgets/utils/progressbar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import contextlib
import time
import warnings

from PyQt4.QtCore import pyqtSignal as Signal, pyqtProperty, QEventLoop
from PyQt4.QtGui import qApp

from Orange.widgets import gui

class ProgressBarMixin:
# Set these here so we avoid having to call `__init__` fromm classes
# that use this mix-in
__progressBarValue = -1
__progressState = 0
startTime = time.time() # used in progressbar

def progressBarInit(self, processEvents=QEventLoop.AllEvents):
"""
Initialize the widget's progress (i.e show and set progress to 0%).
.. note::
This method will by default call `QApplication.processEvents`
with `processEvents`. To suppress this behavior pass
``processEvents=None``.
:param processEvents: Process events flag
:type processEvents: `QEventLoop.ProcessEventsFlags` or `None`
"""
self.startTime = time.time()
self.setWindowTitle(self.captionTitle + " (0% complete)")

if self.__progressState != 1:
self.__progressState = 1
self.processingStateChanged.emit(1)

self.progressBarSet(0, processEvents)

def progressBarSet(self, value, processEvents=QEventLoop.AllEvents):
"""
Set the current progress bar to `value`.
.. note::
This method will by default call `QApplication.processEvents`
with `processEvents`. To suppress this behavior pass
``processEvents=None``.
:param float value: Progress value
:param processEvents: Process events flag
:type processEvents: `QEventLoop.ProcessEventsFlags` or `None`
"""
old = self.__progressBarValue
self.__progressBarValue = value

if value > 0:
if self.__progressState != 1:
warnings.warn("progressBarSet() called without a "
"preceding progressBarInit()",
stacklevel=2)
self.__progressState = 1
self.processingStateChanged.emit(1)

usedTime = max(1, time.time() - self.startTime)
totalTime = 100.0 * usedTime / value
remainingTime = max(0, int(totalTime - usedTime))
hrs = remainingTime // 3600
mins = (remainingTime % 3600) // 60
secs = remainingTime % 60
if hrs > 0:
text = "{}:{:02}:{:02}".format(hrs, mins, secs)
else:
text = "{}:{}:{:02}".format(hrs, mins, secs)
self.setWindowTitle("{} ({:.2f}% complete, remaining time: {})"
.format(self.captionTitle, value, text))
else:
self.setWindowTitle(self.captionTitle + " (0% complete)")

if old != value:
self.progressBarValueChanged.emit(value)

if processEvents is not None and processEvents is not False:
qApp.processEvents(processEvents)

def progressBarValue(self):
"""Return the state of the progress bar
"""
return self.__progressBarValue

progressBarValue = pyqtProperty(
float, fset=progressBarSet, fget=progressBarValue)
processingState = pyqtProperty(int, fget=lambda self: self.__progressState)

def progressBarAdvance(self, value, processEvents=QEventLoop.AllEvents):
"""
Advance the progress bar.
.. note::
This method will by default call `QApplication.processEvents`
with `processEvents`. To suppress this behavior pass
``processEvents=None``.
Args:
value (int): progress value
processEvents (`QEventLoop.ProcessEventsFlags` or `None`):
process events flag
"""
self.progressBarSet(self.progressBarValue + value, processEvents)

def progressBarFinished(self, processEvents=QEventLoop.AllEvents):
"""
Stop the widget's progress (i.e hide the progress bar).
.. note::
This method will by default call `QApplication.processEvents`
with `processEvents`. To suppress this behavior pass
``processEvents=None``.
:param processEvents: Process events flag
:type processEvents: `QEventLoop.ProcessEventsFlags` or `None`
"""
self.setWindowTitle(self.captionTitle)
if self.__progressState != 0:
self.__progressState = 0
self.processingStateChanged.emit(0)

if processEvents is not None and processEvents is not False:
qApp.processEvents(processEvents)

@contextlib.contextmanager
def progressBar(self, iterations=0):
"""
Context manager for progress bar.
Using it ensures that the progress bar is removed at the end without
needing the `finally` blocks.
Usage:
with self.progressBar(20) as progress:
...
progress.advance()
or
with self.progressBar() as progress:
...
progress.advance(0.15)
or
with self.progressBar():
...
self.progressBarSet(50)
:param iterations: the number of iterations (optional)
:type iterations: int
"""
progress_bar = gui.ProgressBar(self, iterations)
yield progress_bar
progress_bar.finish() # Let us not rely on garbage collector
17 changes: 7 additions & 10 deletions Orange/widgets/utils/scaling.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ class ScaleData:
def __init__(self):
self.raw_data = None # input data
self.attribute_names = [] # list of attribute names from self.raw_data
self.attribute_name_index = {} # dict with indices to attributes
self.attribute_flip_info = {} # dictionary with attrName: 0/1 attribute is flipped or not

self.data_has_class = False
Expand Down Expand Up @@ -111,8 +110,6 @@ def set_data(self, data, **args):
len_data = data and len(data) or 0

self.attribute_names = [attr.name for attr in full_data.domain]
self.attribute_name_index = dict([(full_data.domain[i].name, i)
for i in range(len(full_data.domain))])
self.attribute_flip_info = {}

self.data_domain = full_data.domain
Expand All @@ -122,7 +119,7 @@ def set_data(self, data, **args):

self.data_class_name = self.data_has_class and full_data.domain.class_var.name
if self.data_has_class:
self.data_class_index = self.attribute_name_index[self.data_class_name]
self.data_class_index = self.data_domain.index(self.data_class_name)
self.have_data = bool(self.raw_data and len(self.raw_data) > 0)

self.domain_data_stat = getCached(full_data,
Expand Down Expand Up @@ -244,7 +241,7 @@ def flip_attribute(self, attr_name):
if self.data_domain[attr_name].is_discrete:
return 0

index = self.attribute_name_index[attr_name]
index = self.data_domain.index(attr_name)
self.attribute_flip_info[attr_name] = 1 - self.attribute_flip_info.get(attr_name, 0)
if self.data_domain[attr_name].is_continuous:
self.attr_values[attr_name] = [-self.attr_values[attr_name][1], -self.attr_values[attr_name][0]]
Expand Down Expand Up @@ -307,8 +304,8 @@ def get_xy_data_positions(self, xattr, yattr, filter_valid=False,
Create x-y projection of attributes in attrlist.
"""
xattr_index = self.attribute_name_index[xattr]
yattr_index = self.attribute_name_index[yattr]
xattr_index = self.data_domain.index(xattr)
yattr_index = self.data_domain.index(yattr)
if filter_valid is True:
filter_valid = self.get_valid_list([xattr_index, yattr_index])
if isinstance(filter_valid, np.ndarray):
Expand Down Expand Up @@ -494,9 +491,9 @@ def get_optimal_clusters(self, attribute_name_order, add_result_funct):
for i in range(len(attribute_name_order)):
for j in range(i):
try:
index = self.attribute_name_index
attr1 = index[attribute_name_order[j]]
attr2 = index[attribute_name_order[i]]
index = self.data_domain.index
attr1 = index(attribute_name_order[j])
attr2 = index(attribute_name_order[i])
test_index += 1
if self.clusterOptimization.isOptimizationCanceled():
secs = time.time() - start_time
Expand Down
Loading

0 comments on commit 816858d

Please sign in to comment.