class Plotter(QObject): def __init__(self, dimensions, main_title, x_title, y_title): QObject.__init__(self) self.dimensions = dimensions self.scene = QGraphicsScene() self.main_title = TextItem(main_title, self.scene, self.dimensions, main_title_transform) self.x_title = TextItem(x_title, self.scene, self.dimensions, x_title_transform) self.y_title = TextItem(y_title, self.scene, self.dimensions, y_title_transform) if config.debug.plotter.show_margin_boxes: self.debug_margin_boxes = [] dimensions.changed.connect(self.show_margin_boxes) self.show_margin_boxes() def show_margin_boxes(self): for rect in self.debug_margin_boxes: self.scene.removeItem(rect) r = [] r.append(self.scene.addRect(0,0, self.dimensions.left_margin, self.dimensions.height, red, red_t)) r.append(self.scene.addRect(0,0, self.dimensions.width, self.dimensions.top_margin, green, green_t)) r.append(self.scene.addRect(0, self.dimensions.top_margin + self.dimensions.grid_height, self.dimensions.width, self.dimensions.bottom_margin, blue, blue_t)) r.append(self.scene.addRect(self.dimensions.width, self.dimensions.height, -self.dimensions.right_margin, -self.dimensions.height, cyan, cyan_t)) halfway_height = self.dimensions.top_margin + self.dimensions.grid_height/2 r.append(self.scene.addLine(0, halfway_height, self.dimensions.width, halfway_height, QPen(black, 0))) halfway_width = self.dimensions.left_margin + self.dimensions.grid_width/2 r.append(self.scene.addLine(halfway_width, 0, halfway_width, self.dimensions.height, QPen(black, 0))) self.debug_margin_boxes = r
class OWSieveDiagram(OWWidget): name = "Sieve Diagram" description = "Visualize the observed and expected frequencies " \ "for a combination of values." icon = "icons/SieveDiagram.svg" priority = 200 inputs = [("Data", Table, "set_data", Default), ("Features", AttributeList, "set_input_features")] outputs = [("Selection", Table)] graph_name = "canvas" want_control_area = False settingsHandler = DomainContextHandler() attrX = ContextSetting("", exclude_metas=False) attrY = ContextSetting("", exclude_metas=False) selection = ContextSetting(set()) def __init__(self): # pylint: disable=missing-docstring super().__init__() self.data = self.discrete_data = None self.attrs = [] self.input_features = None self.areas = [] self.selection = set() self.attr_box = gui.hBox(self.mainArea) model = VariableListModel() model.wrap(self.attrs) combo_args = dict(widget=self.attr_box, master=self, contentsLength=12, callback=self.update_attr, sendSelectedValue=True, valueType=str, model=model) fixed_size = (QSizePolicy.Fixed, QSizePolicy.Fixed) self.attrXCombo = gui.comboBox(value="attrX", **combo_args) gui.widgetLabel(self.attr_box, "\u2715", sizePolicy=fixed_size) self.attrYCombo = gui.comboBox(value="attrY", **combo_args) self.vizrank, self.vizrank_button = SieveRank.add_vizrank( self.attr_box, self, "Score Combinations", self.set_attr) self.vizrank_button.setSizePolicy(*fixed_size) self.canvas = QGraphicsScene() self.canvasView = ViewWithPress(self.canvas, self.mainArea, handler=self.reset_selection) self.mainArea.layout().addWidget(self.canvasView) self.canvasView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.canvasView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) box = gui.hBox(self.mainArea) box.layout().addWidget(self.graphButton) box.layout().addWidget(self.report_button) def sizeHint(self): return QSize(450, 550) def resizeEvent(self, event): super().resizeEvent(event) self.update_graph() def showEvent(self, event): super().showEvent(event) self.update_graph() def set_data(self, data): """ Discretize continuous attributes, and put all attributes and discrete metas into self.attrs, which is used as a model for combos. Select the first two attributes unless context overrides this. Method `resolve_shown_attributes` is called to use the attributes from the input, if it exists and matches the attributes in the data. Remove selection; again let the context override this. Initialize the vizrank dialog, but don't show it. Args: data (Table): input data """ if isinstance(data, SqlTable) and data.approx_len() > LARGE_TABLE: data = data.sample_time(DEFAULT_SAMPLE_TIME) self.closeContext() self.data = data self.areas = [] self.selection = set() if self.data is None: self.attrs[:] = [] else: if any(attr.is_continuous for attr in data.domain): discretizer = Discretize(method=EqualFreq(n=4), discretize_classes=True, discretize_metas=True) self.discrete_data = discretizer(data) else: self.discrete_data = self.data self.attrs[:] = [ var for var in chain(self.discrete_data.domain, ( var for var in self.data.domain.metas if var.is_discrete)) ] if self.attrs: self.attrX = self.attrs[0].name self.attrY = self.attrs[len(self.attrs) > 1].name else: self.attrX = self.attrY = None self.areas = [] self.selection = set() self.openContext(self.data) self.resolve_shown_attributes() self.update_graph() self.update_selection() self.vizrank.initialize() self.vizrank_button.setEnabled( self.data is not None and len(self.data) > 1 and len(self.data.domain.attributes) > 1) def set_attr(self, attr_x, attr_y): self.attrX, self.attrY = attr_x.name, attr_y.name self.update_attr() def update_attr(self): """Update the graph and selection.""" self.selection = set() self.update_graph() self.update_selection() def set_input_features(self, attr_list): """ Handler for the Features signal. The method stores the attributes and calls `resolve_shown_attributes` Args: attr_list (AttributeList): data from the signal """ self.input_features = attr_list self.resolve_shown_attributes() self.update_selection() def resolve_shown_attributes(self): """ Use the attributes from the input signal if the signal is present and at least two attributes appear in the domain. If there are multiple, use the first two. Combos are disabled if inputs are used. """ self.warning() self.attr_box.setEnabled(True) if not self.input_features: # None or empty return features = [f for f in self.input_features if f in self.attrs] if not features: self.warning( "Features from the input signal are not present in the data") return old_attrs = self.attrX, self.attrY self.attrX, self.attrY = [f.name for f in (features * 2)[:2]] self.attr_box.setEnabled(False) if (self.attrX, self.attrY) != old_attrs: self.selection = set() self.update_graph() def reset_selection(self): self.selection = set() self.update_selection() def select_area(self, area, event): """ Add or remove the clicked area from the selection Args: area (QRect): the area that is clicked event (QEvent): event description """ if event.button() != Qt.LeftButton: return index = self.areas.index(area) if event.modifiers() & Qt.ControlModifier: self.selection ^= {index} else: self.selection = {index} self.update_selection() def update_selection(self): """ Update the graph (pen width) to show the current selection. Filter and output the data. """ if self.areas is None or not self.selection: self.send("Selection", None) return filts = [] for i, area in enumerate(self.areas): if i in self.selection: width = 4 val_x, val_y = area.value_pair filts.append( filter.Values([ filter.FilterDiscrete(self.attrX, [val_x]), filter.FilterDiscrete(self.attrY, [val_y]) ])) else: width = 1 pen = area.pen() pen.setWidth(width) area.setPen(pen) if len(filts) == 1: filts = filts[0] else: filts = filter.Values(filts, conjunction=False) selection = filts(self.discrete_data) if self.discrete_data is not self.data: idset = set(selection.ids) sel_idx = [i for i, id in enumerate(self.data.ids) if id in idset] selection = self.data[sel_idx] self.send("Selection", selection) def update_graph(self): # Function uses weird names like r, g, b, but it does it with utmost # caution, hence # pylint: disable=invalid-name """Update the graph.""" def text(txt, *args, **kwargs): return CanvasText(self.canvas, "", html_text=to_html(txt), *args, **kwargs) def width(txt): return text(txt, 0, 0, show=False).boundingRect().width() def fmt(val): return str(int(val)) if val % 1 == 0 else "{:.2f}".format(val) def show_pearson(rect, pearson, pen_width): """ Color the given rectangle according to its corresponding standardized Pearson residual. Args: rect (QRect): the rectangle being drawn pearson (float): signed standardized pearson residual pen_width (int): pen width (bolder pen is used for selection) """ r = rect.rect() x, y, w, h = r.x(), r.y(), r.width(), r.height() if w == 0 or h == 0: return r = b = 255 if pearson > 0: r = g = max(255 - 20 * pearson, 55) elif pearson < 0: b = g = max(255 + 20 * pearson, 55) else: r = g = b = 224 rect.setBrush(QBrush(QColor(r, g, b))) pen_color = QColor(255 * (r == 255), 255 * (g == 255), 255 * (b == 255)) pen = QPen(pen_color, pen_width) rect.setPen(pen) if pearson > 0: pearson = min(pearson, 10) dist = 20 - 1.6 * pearson else: pearson = max(pearson, -10) dist = 20 - 8 * pearson pen.setWidth(1) def _offseted_line(ax, ay): r = QGraphicsLineItem(x + ax, y + ay, x + (ax or w), y + (ay or h)) self.canvas.addItem(r) r.setPen(pen) ax = dist while ax < w: _offseted_line(ax, 0) ax += dist ay = dist while ay < h: _offseted_line(0, ay) ay += dist def make_tooltip(): """Create the tooltip. The function uses local variables from the enclosing scope.""" # pylint: disable=undefined-loop-variable def _oper(attr_name, txt): if self.data.domain[attr_name] is ddomain[attr_name]: return "=" return " " if txt[0] in "<≥" else " in " return ("<b>{attrX}{xeq}{xval_name}</b>: {obs_x}/{n} ({p_x:.0f} %)" .format(attrX=to_html(attr_x), xeq=_oper(attr_x, xval_name), xval_name=to_html(xval_name), obs_x=fmt(chi.probs_x[x] * n), n=int(n), p_x=100 * chi.probs_x[x]) + "<br/>" + "<b>{attrY}{yeq}{yval_name}</b>: {obs_y}/{n} ({p_y:.0f} %)" .format(attrY=to_html(attr_y), yeq=_oper(attr_y, yval_name), yval_name=to_html(yval_name), obs_y=fmt(chi.probs_y[y] * n), n=int(n), p_y=100 * chi.probs_y[y]) + "<hr/>" + """<b>combination of values: </b><br/> expected {exp} ({p_exp:.0f} %)<br/> observed {obs} ({p_obs:.0f} %)""".format( exp=fmt(chi.expected[y, x]), p_exp=100 * chi.expected[y, x] / n, obs=fmt(chi.observed[y, x]), p_obs=100 * chi.observed[y, x] / n)) for item in self.canvas.items(): self.canvas.removeItem(item) if self.data is None or len(self.data) == 0 or \ self.attrX is None or self.attrY is None: return ddomain = self.discrete_data.domain attr_x, attr_y = self.attrX, self.attrY disc_x, disc_y = ddomain[attr_x], ddomain[attr_y] view = self.canvasView chi = ChiSqStats(self.discrete_data, attr_x, attr_y) n = chi.n max_ylabel_w = max((width(val) for val in disc_y.values), default=0) max_ylabel_w = min(max_ylabel_w, 200) x_off = width(attr_x) + max_ylabel_w y_off = 15 square_size = min(view.width() - x_off - 35, view.height() - y_off - 50) square_size = max(square_size, 10) self.canvasView.setSceneRect(0, 0, view.width(), view.height()) curr_x = x_off max_xlabel_h = 0 self.areas = [] for x, (px, xval_name) in enumerate(zip(chi.probs_x, disc_x.values)): if px == 0: continue width = square_size * px curr_y = y_off for y in range(len(chi.probs_y) - 1, -1, -1): # bottom-up order py = chi.probs_y[y] yval_name = disc_y.values[y] if py == 0: continue height = square_size * py selected = len(self.areas) in self.selection rect = CanvasRectangle(self.canvas, curr_x + 2, curr_y + 2, width - 4, height - 4, z=-10, onclick=self.select_area) rect.value_pair = x, y self.areas.append(rect) show_pearson(rect, chi.residuals[y, x], 3 * selected) rect.setToolTip(make_tooltip()) if x == 0: text(yval_name, x_off, curr_y + height / 2, Qt.AlignRight | Qt.AlignVCenter) curr_y += height xl = text(xval_name, curr_x + width / 2, y_off + square_size, Qt.AlignHCenter | Qt.AlignTop) max_xlabel_h = max(int(xl.boundingRect().height()), max_xlabel_h) curr_x += width bottom = y_off + square_size + max_xlabel_h text(attr_y, 0, y_off + square_size / 2, Qt.AlignLeft | Qt.AlignVCenter, bold=True, vertical=True) text(attr_x, x_off + square_size / 2, bottom, Qt.AlignHCenter | Qt.AlignTop, bold=True) xl = text("χ²={:.2f}, p={:.3f}".format(chi.chisq, chi.p), 0, bottom) # Assume similar height for both lines text("N = " + fmt(chi.n), 0, bottom - xl.boundingRect().height()) def get_widget_name_extension(self): if self.data is not None: return "{} vs {}".format(self.attrX, self.attrY) def send_report(self): self.report_plot()
class FlowChartView(QWidget): """ Flowchart view """ def __init__(self, parent): """ Constructs FlowChartView widget @param parent: @type parent: """ QWidget.__init__(self, parent) self.steps = [] self.timestamps = [] self.arrows = [] self.createWidget() def createWidget(self): """ Create the widget """ self.diagramScene = QGraphicsScene(self) self.view = QGraphicsView(self.diagramScene) self.view.setRenderHint(QPainter.Antialiasing) # set the main layout layout = QVBoxLayout() self.logEdit = QTextEdit() self.logEdit.setReadOnly(True) hSplitter2 = QSplitter(self) hSplitter2.setOrientation(Qt.Vertical) hSplitter2.addWidget(self.view) hSplitter2.addWidget(self.logEdit) hSplitter2.setStretchFactor(0, 1) layout.addWidget(hSplitter2) self.setLayout(layout) def reset(self): """ Clear all """ #self.diagramScene.clear() for stp in self.steps: self.diagramScene.removeItem(stp) for stp in self.arrows: self.diagramScene.removeItem(stp) for stamp in self.timestamps: self.diagramScene.removeItem(stamp) self.diagramScene.clear() self.diagramScene.update() self.view.resetCachedContent() self.steps = [] self.arrows = [] self.timestamps = [] self.logEdit.setText("") def addStep(self, text, color="#A5A2A5", width=400, height=40, data=None, textBold=False, textItalic=False, timestamp="00:00:00"): """ Add step """ # take the last one if len(self.steps): latestBlock = self.steps[-1:][0] else: latestBlock = None newBlock = BlockItem(self, text, blockColor=color, width=width, height=height, data=data, bold=textBold, italic=textItalic) if width == 100: newBlock.setPos(400 / 2 - 100 / 2, len(self.steps) * 80) elif width == 300: newBlock.setPos(400 / 2 - 300 / 2, len(self.steps) * 80) else: newBlock.setPos(0, len(self.steps) * 80) self.steps.append(newBlock) self.diagramScene.addItem(newBlock) newTimestampBlock = TimestampItem(self, timestamp) newTimestampBlock.setPos(-200, len(self.timestamps) * 80) self.timestamps.append(newTimestampBlock) self.diagramScene.addItem(newTimestampBlock) if latestBlock is not None: newArrow = LineItem(latestBlock, newBlock) self.diagramScene.addItem(newArrow) self.arrows.append(newArrow) if QtHelper.str2bool(Settings.instance().readValue( key='TestRun/auto-scrolling-graph')): self.view.centerOn(newBlock) return newBlock
class OWSieveDiagram(OWWidget): """ A two-way contingency table providing information on the relation between the observed and expected frequencies of a combination of values """ name = "Sieve Diagram" icon = "icons/SieveDiagram.svg" priority = 310 inputs = [("Data", Table, "set_data", Default), ("Features", AttributeList, "set_input_features")] outputs = [("Selection", Table)] graph_name = "canvas" want_control_area = False settingsHandler = DomainContextHandler() attrX = ContextSetting("") attrY = ContextSetting("") selection = ContextSetting(set()) def __init__(self): # pylint: disable=missing-docstring super().__init__() self.data = self.discrete_data = None self.attrs = [] self.input_features = None self.areas = [] self.selection = set() self.attr_box = gui.hBox(self.mainArea) model = VariableListModel() model.wrap(self.attrs) combo_args = dict( widget=self.attr_box, master=self, contentsLength=12, callback=self.update_attr, sendSelectedValue=True, valueType=str, model=model) fixed_size = (QSizePolicy.Fixed, QSizePolicy.Fixed) self.attrXCombo = gui.comboBox(value="attrX", **combo_args) gui.widgetLabel(self.attr_box, "\u2715", sizePolicy=fixed_size) self.attrYCombo = gui.comboBox(value="attrY", **combo_args) self.vizrank = SieveRank(self) self.vizrank_button = gui.button( self.attr_box, self, "Score Combinations", sizePolicy=fixed_size, callback=self.vizrank.reshow, enabled=False) self.vizrank.pairSelected.connect(self.set_attr) self.canvas = QGraphicsScene() self.canvasView = ViewWithPress( self.canvas, self.mainArea, handler=self.reset_selection) self.mainArea.layout().addWidget(self.canvasView) self.canvasView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.canvasView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) box = gui.hBox(self.mainArea) box.layout().addWidget(self.graphButton) box.layout().addWidget(self.report_button) def sizeHint(self): return QSize(450, 550) def resizeEvent(self, event): super().resizeEvent(event) self.update_graph() def showEvent(self, event): super().showEvent(event) self.update_graph() def closeEvent(self, event): self.vizrank.close() super().closeEvent(event) def hideEvent(self, event): self.vizrank.hide() super().hideEvent(event) def set_data(self, data): """ Discretize continuous attributes, and put all attributes and discrete metas into self.attrs, which is used as a model for combos. Select the first two attributes unless context overrides this. Method `resolve_shown_attributes` is called to use the attributes from the input, if it exists and matches the attributes in the data. Remove selection; again let the context override this. Initialize the vizrank dialog, but don't show it. Args: data (Table): input data """ if isinstance(data, SqlTable) and data.approx_len() > LARGE_TABLE: data = data.sample_time(DEFAULT_SAMPLE_TIME) self.closeContext() self.data = data self.areas = [] self.selection = set() if self.data is None: self.attrs[:] = [] else: if any(attr.is_continuous for attr in data.domain): discretizer = Discretize( method=EqualFreq(n=4), discretize_classes=True, discretize_metas=True) self.discrete_data = discretizer(data) else: self.discrete_data = self.data self.attrs[:] = [ var for var in chain( self.discrete_data.domain, (var for var in self.data.domain.metas if var.is_discrete)) ] if self.attrs: self.attrX = self.attrs[0].name self.attrY = self.attrs[len(self.attrs) > 1].name else: self.attrX = self.attrY = None self.areas = [] self.selection = set() self.openContext(self.data) self.resolve_shown_attributes() self.update_graph() self.update_selection() self.vizrank.initialize() self.vizrank_button.setEnabled( self.data is not None and len(self.data) > 1 and len(self.data.domain.attributes) > 1) def set_attr(self, attr_x, attr_y): self.attrX, self.attrY = attr_x.name, attr_y.name self.update_attr() def update_attr(self): """Update the graph and selection.""" self.selection = set() self.update_graph() self.update_selection() def set_input_features(self, attr_list): """ Handler for the Features signal. The method stores the attributes and calls `resolve_shown_attributes` Args: attr_list (AttributeList): data from the signal """ self.input_features = attr_list self.resolve_shown_attributes() self.update_selection() def resolve_shown_attributes(self): """ Use the attributes from the input signal if the signal is present and at least two attributes appear in the domain. If there are multiple, use the first two. Combos are disabled if inputs are used. """ self.warning() self.attr_box.setEnabled(True) if not self.input_features: # None or empty return features = [f for f in self.input_features if f in self.attrs] if not features: self.warning( "Features from the input signal are not present in the data") return old_attrs = self.attrX, self.attrY self.attrX, self.attrY = [f.name for f in (features * 2)[:2]] self.attr_box.setEnabled(False) if (self.attrX, self.attrY) != old_attrs: self.selection = set() self.update_graph() def reset_selection(self): self.selection = set() self.update_selection() def select_area(self, area, event): """ Add or remove the clicked area from the selection Args: area (QRect): the area that is clicked event (QEvent): event description """ if event.button() != Qt.LeftButton: return index = self.areas.index(area) if event.modifiers() & Qt.ControlModifier: self.selection ^= {index} else: self.selection = {index} self.update_selection() def update_selection(self): """ Update the graph (pen width) to show the current selection. Filter and output the data. """ if self.areas is None or not self.selection: self.send("Selection", None) return filts = [] for i, area in enumerate(self.areas): if i in self.selection: width = 4 val_x, val_y = area.value_pair filts.append( filter.Values([ filter.FilterDiscrete(self.attrX, [val_x]), filter.FilterDiscrete(self.attrY, [val_y]) ])) else: width = 1 pen = area.pen() pen.setWidth(width) area.setPen(pen) if len(filts) == 1: filts = filts[0] else: filts = filter.Values(filts, conjunction=False) selection = filts(self.discrete_data) if self.discrete_data is not self.data: idset = set(selection.ids) sel_idx = [i for i, id in enumerate(self.data.ids) if id in idset] selection = self.data[sel_idx] self.send("Selection", selection) def update_graph(self): # Function uses weird names like r, g, b, but it does it with utmost # caution, hence # pylint: disable=invalid-name """Update the graph.""" def text(txt, *args, **kwargs): return CanvasText(self.canvas, "", html_text=to_html(txt), *args, **kwargs) def width(txt): return text(txt, 0, 0, show=False).boundingRect().width() def fmt(val): return str(int(val)) if val % 1 == 0 else "{:.2f}".format(val) def show_pearson(rect, pearson, pen_width): """ Color the given rectangle according to its corresponding standardized Pearson residual. Args: rect (QRect): the rectangle being drawn pearson (float): signed standardized pearson residual pen_width (int): pen width (bolder pen is used for selection) """ r = rect.rect() x, y, w, h = r.x(), r.y(), r.width(), r.height() if w == 0 or h == 0: return r = b = 255 if pearson > 0: r = g = max(255 - 20 * pearson, 55) elif pearson < 0: b = g = max(255 + 20 * pearson, 55) else: r = g = b = 224 rect.setBrush(QBrush(QColor(r, g, b))) pen_color = QColor(255 * (r == 255), 255 * (g == 255), 255 * (b == 255)) pen = QPen(pen_color, pen_width) rect.setPen(pen) if pearson > 0: pearson = min(pearson, 10) dist = 20 - 1.6 * pearson else: pearson = max(pearson, -10) dist = 20 - 8 * pearson pen.setWidth(1) def _offseted_line(ax, ay): r = QGraphicsLineItem(x + ax, y + ay, x + (ax or w), y + (ay or h)) self.canvas.addItem(r) r.setPen(pen) ax = dist while ax < w: _offseted_line(ax, 0) ax += dist ay = dist while ay < h: _offseted_line(0, ay) ay += dist def make_tooltip(): """Create the tooltip. The function uses local variables from the enclosing scope.""" # pylint: disable=undefined-loop-variable def _oper(attr_name, txt): if self.data.domain[attr_name] is ddomain[attr_name]: return "=" return " " if txt[0] in "<≥" else " in " return ( "<b>{attrX}{xeq}{xval_name}</b>: {obs_x}/{n} ({p_x:.0f} %)". format(attrX=to_html(attr_x), xeq=_oper(attr_x, xval_name), xval_name=to_html(xval_name), obs_x=fmt(chi.probs_x[x] * n), n=int(n), p_x=100 * chi.probs_x[x]) + "<br/>" + "<b>{attrY}{yeq}{yval_name}</b>: {obs_y}/{n} ({p_y:.0f} %)". format(attrY=to_html(attr_y), yeq=_oper(attr_y, yval_name), yval_name=to_html(yval_name), obs_y=fmt(chi.probs_y[y] * n), n=int(n), p_y=100 * chi.probs_y[y]) + "<hr/>" + """<b>combination of values: </b><br/> expected {exp} ({p_exp:.0f} %)<br/> observed {obs} ({p_obs:.0f} %)""". format(exp=fmt(chi.expected[y, x]), p_exp=100 * chi.expected[y, x] / n, obs=fmt(chi.observed[y, x]), p_obs=100 * chi.observed[y, x] / n)) for item in self.canvas.items(): self.canvas.removeItem(item) if self.data is None or len(self.data) == 0 or \ self.attrX is None or self.attrY is None: return ddomain = self.discrete_data.domain attr_x, attr_y = self.attrX, self.attrY disc_x, disc_y = ddomain[attr_x], ddomain[attr_y] view = self.canvasView chi = ChiSqStats(self.discrete_data, attr_x, attr_y) n = chi.n max_ylabel_w = max((width(val) for val in disc_y.values), default=0) max_ylabel_w = min(max_ylabel_w, 200) x_off = width(attr_x) + max_ylabel_w y_off = 15 square_size = min(view.width() - x_off - 35, view.height() - y_off - 50) square_size = max(square_size, 10) self.canvasView.setSceneRect(0, 0, view.width(), view.height()) curr_x = x_off max_xlabel_h = 0 self.areas = [] for x, (px, xval_name) in enumerate(zip(chi.probs_x, disc_x.values)): if px == 0: continue width = square_size * px curr_y = y_off for y in range(len(chi.probs_y) - 1, -1, -1): # bottom-up order py = chi.probs_y[y] yval_name = disc_y.values[y] if py == 0: continue height = square_size * py selected = len(self.areas) in self.selection rect = CanvasRectangle( self.canvas, curr_x + 2, curr_y + 2, width - 4, height - 4, z=-10, onclick=self.select_area) rect.value_pair = x, y self.areas.append(rect) show_pearson(rect, chi.residuals[y, x], 3 * selected) rect.setToolTip(make_tooltip()) if x == 0: text(yval_name, x_off, curr_y + height / 2, Qt.AlignRight | Qt.AlignVCenter) curr_y += height xl = text(xval_name, curr_x + width / 2, y_off + square_size, Qt.AlignHCenter | Qt.AlignTop) max_xlabel_h = max(int(xl.boundingRect().height()), max_xlabel_h) curr_x += width bottom = y_off + square_size + max_xlabel_h text(attr_y, 0, y_off + square_size / 2, Qt.AlignLeft | Qt.AlignVCenter, bold=True, vertical=True) text(attr_x, x_off + square_size / 2, bottom, Qt.AlignHCenter | Qt.AlignTop, bold=True) xl = text("χ²={:.2f}, p={:.3f}".format(chi.chisq, chi.p), 0, bottom) # Assume similar height for both lines text("N = " + fmt(chi.n), 0, bottom - xl.boundingRect().height()) def get_widget_name_extension(self): if self.data is not None: return "{} vs {}".format(self.attrX, self.attrY) def send_report(self): self.report_plot()
class OWSieveDiagram(OWWidget): name = "Sieve Diagram" icon = "icons/SieveDiagram.svg" priority = 4200 inputs = [("Data", Table, "setData", Default), ("Features", AttributeList, "setShownAttributes")] outputs = [] settingsList = ["showLines", "showCases", "showInColor"] def __init__(self, parent=None, signalManager=None): OWWidget.__init__(self, parent, signalManager, "Sieve diagram", True) #self.controlArea.setMinimumWidth(250) #set default settings self.data = None self.attrX = "" self.attrY = "" self.attrCondition = "" self.attrConditionValue = "" self.showLines = 1 self.showCases = 0 self.showInColor = 1 self.attributeSelectionList = None self.stopCalculating = 0 self.canvas = QGraphicsScene() self.canvasView = QGraphicsView(self.canvas, self.mainArea) self.mainArea.layout().addWidget(self.canvasView) self.canvasView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.canvasView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) #GUI self.attrSelGroup = gui.widgetBox(self.controlArea, box="Shown attributes") self.attrXCombo = gui.comboBox( self.attrSelGroup, self, value="attrX", label="X attribute:", orientation="horizontal", tooltip="Select an attribute to be shown on the X axis", callback=self.updateGraph, sendSelectedValue=1, valueType=str, labelWidth=70) self.attrYCombo = gui.comboBox( self.attrSelGroup, self, value="attrY", label="Y attribute:", orientation="horizontal", tooltip="Select an attribute to be shown on the Y axis", callback=self.updateGraph, sendSelectedValue=1, valueType=str, labelWidth=70) gui.separator(self.controlArea) self.conditionGroup = gui.widgetBox(self.controlArea, box="Condition") self.attrConditionCombo = gui.comboBox( self.conditionGroup, self, value="attrCondition", label="Attribute:", orientation="horizontal", callback=self.updateConditionAttr, sendSelectedValue=1, valueType=str, labelWidth=70) self.attrConditionValueCombo = gui.comboBox(self.conditionGroup, self, value="attrConditionValue", label="Value:", orientation="horizontal", callback=self.updateGraph, sendSelectedValue=1, valueType=str, labelWidth=70) gui.separator(self.controlArea) box2 = gui.widgetBox(self.controlArea, box="Visual settings") gui.checkBox(box2, self, "showLines", "Show lines", callback=self.updateGraph) hbox = gui.widgetBox(box2, orientation="horizontal") gui.checkBox(hbox, self, "showCases", "Show data examples...", callback=self.updateGraph) gui.checkBox(hbox, self, "showInColor", "...in color", callback=self.updateGraph) gui.separator(self.controlArea) # self.optimizationDlg = OWSieveOptimization(self, self.signalManager) # optimizationButtons = gui.widgetBox(self.controlArea, "Dialogs", orientation = "horizontal") # gui.button(optimizationButtons, self, "VizRank", callback = self.optimizationDlg.reshow, debuggingEnabled = 0, tooltip = "Find attribute groups with highest value dependency") gui.rubber(self.controlArea) # self.wdChildDialogs = [self.optimizationDlg] # used when running widget debugging # self.graphButton.clicked.connect(self.saveToFileCanvas) self.icons = gui.attributeIconDict self.resize(800, 550) random.seed() def sendReport(self): self.startReport("%s [%s, %s]" % (self.windowTitle(), self.attrX, self.attrY)) self.reportSettings( "", [("X-Attribute", self.attrX), ("Y-Attribute", self.attrY), self.attrCondition != "(None)" and ("Condition", "%s = '%s'" % (self.attrCondition, self.attrConditionValue))]) # self.reportImage(lambda *x: OWChooseImageSizeDlg(self.canvas).saveImage(*x)) # receive new data and update all fields def setData(self, data): if type(data) == SqlTable and data.approx_len() > LARGE_TABLE: data = data.sample_time(DEFAULT_SAMPLE_TIME) self.information(0) self.information(1) sameDomain = self.data and data and self.data.domain.checksum( ) == data.domain.checksum( ) # preserve attribute choice if the domain is the same # self.data = self.optimizationDlg.setData(data, 0) self.data = data self.warning(0, "") if data: if any( isinstance(attr, ContinuousVariable) for attr in data.domain): self.warning( 0, "Data contains continuous variables. " + "Discretize the data to use them.") if not sameDomain: self.initCombos() self.setShownAttributes(self.attributeSelectionList) ## Attribute selection signal def setShownAttributes(self, attrList): self.attributeSelectionList = attrList if self.data and self.attributeSelectionList and len(attrList) >= 2: attrs = [attr.name for attr in self.data.domain] if attrList[0] in attrs and attrList[1] in attrs: self.attrX = attrList[0] self.attrY = attrList[1] self.updateGraph() # create data subset depending on conditional attribute and value def getConditionalData(self, xAttr=None, yAttr=None, dropMissingData=1): if not self.data: return None if not xAttr: xAttr = self.attrX if not yAttr: yAttr = self.attrY if not (xAttr and yAttr): return if self.attrCondition == "(None)": data = self.data[:, [xAttr, yAttr]] # data = self.data.select([xAttr, yAttr]) else: # data = orange.Preprocessor_dropMissing(self.data.select([xAttr, yAttr, self.attrCondition])) # data = self.data.select({self.attrCondition:self.attrConditionValue}) fd = Orange.data.filter.FilterDiscrete( column=self.attrCondition, values=[self.attrConditionValue]) filt = Orange.data.filter.Values([fd]) filt.domain = self.data.domain data = filt(self.data) # if dropMissingData: return orange.Preprocessor_dropMissing(data) #else: return data # new conditional attribute was set - update graph def updateConditionAttr(self): self.attrConditionValueCombo.clear() if self.attrCondition != "(None)": for val in self.data.domain[self.attrCondition].values: self.attrConditionValueCombo.addItem(val) self.attrConditionValue = str( self.attrConditionValueCombo.itemText(0)) self.updateGraph() # initialize lists for shown and hidden attributes def initCombos(self): self.attrXCombo.clear() self.attrYCombo.clear() self.attrConditionCombo.clear() self.attrConditionCombo.addItem("(None)") self.attrConditionValueCombo.clear() if not self.data: return for i, var in enumerate(self.data.domain): if isinstance(var, DiscreteVariable): self.attrXCombo.addItem(self.icons[self.data.domain[i]], self.data.domain[i].name) self.attrYCombo.addItem(self.icons[self.data.domain[i]], self.data.domain[i].name) self.attrConditionCombo.addItem( self.icons[self.data.domain[i]], self.data.domain[i].name) self.attrCondition = str(self.attrConditionCombo.itemText(0)) if self.attrXCombo.count() > 0: self.attrX = str(self.attrXCombo.itemText(0)) self.attrY = str( self.attrYCombo.itemText(self.attrYCombo.count() > 1)) def resizeEvent(self, e): OWWidget.resizeEvent(self, e) self.updateGraph() def showEvent(self, ev): OWWidget.showEvent(self, ev) self.updateGraph() ## updateGraph - gets called every time the graph has to be updated def updateGraph(self, *args): for item in self.canvas.items(): self.canvas.removeItem(item) # remove all canvas items if not self.data: return if not self.attrX or not self.attrY: return data = self.getConditionalData() if not data or len(data) == 0: return valsX = [] valsY = [] # contX = orange.ContingencyAttrAttr(self.attrX, self.attrX, data) # distribution of X attribute # contY = orange.ContingencyAttrAttr(self.attrY, self.attrY, data) # distribution of Y attribute contX = get_contingency(data, self.attrX, self.attrX) contY = get_contingency(data, self.attrY, self.attrY) # compute contingency of x and y attributes for entry in contX: sum_ = 0 try: for val in entry: sum_ += val except: pass valsX.append(sum_) for entry in contY: sum_ = 0 try: for val in entry: sum_ += val except: pass valsY.append(sum_) # create cartesian product of selected attributes and compute contingency # (cart, profit) = FeatureByCartesianProduct(data, [data.domain[self.attrX], data.domain[self.attrY]]) # tempData = data.select(list(data.domain) + [cart]) # contXY = orange.ContingencyAttrAttr(cart, cart, tempData) # distribution of X attribute # contXY = get_contingency(tempData, cart, cart) contXY = self.getConditionalDistributions( data, [data.domain[self.attrX], data.domain[self.attrY]]) # compute probabilities probs = {} for i in range(len(valsX)): valx = valsX[i] for j in range(len(valsY)): valy = valsY[j] actualProb = 0 try: actualProb = contXY['%s-%s' % (data.domain[self.attrX].values[i], data.domain[self.attrY].values[j])] # for val in contXY['%s-%s' %(i, j)]: actualProb += val except: actualProb = 0 probs['%s-%s' % (data.domain[self.attrX].values[i], data.domain[self.attrY].values[j])] = (( data.domain[self.attrX].values[i], valx), (data.domain[self.attrY].values[j], valy), actualProb, len(data)) # get text width of Y attribute name text = OWCanvasText(self.canvas, data.domain[self.attrY].name, x=0, y=0, bold=1, show=0, vertical=True) xOff = int(text.boundingRect().height() + 40) yOff = 50 sqareSize = min(self.canvasView.width() - xOff - 35, self.canvasView.height() - yOff - 30) if sqareSize < 0: return # canvas is too small to draw rectangles self.canvasView.setSceneRect(0, 0, self.canvasView.width(), self.canvasView.height()) # print graph name if self.attrCondition == "(None)": name = "<b>P(%s, %s) ≠ P(%s)×P(%s)</b>" % ( self.attrX, self.attrY, self.attrX, self.attrY) else: name = "<b>P(%s, %s | %s = %s) ≠ P(%s | %s = %s)×P(%s | %s = %s)</b>" % ( self.attrX, self.attrY, self.attrCondition, getHtmlCompatibleString( self.attrConditionValue), self.attrX, self.attrCondition, getHtmlCompatibleString( self.attrConditionValue), self.attrY, self.attrCondition, getHtmlCompatibleString(self.attrConditionValue)) OWCanvasText(self.canvas, "", xOff + sqareSize / 2, 20, Qt.AlignCenter, htmlText=name) OWCanvasText(self.canvas, "N = " + str(len(data)), xOff + sqareSize / 2, 38, Qt.AlignCenter, bold=0) ###################### # compute chi-square chisquare = 0.0 for i in range(len(valsX)): for j in range(len(valsY)): ((xAttr, xVal), (yAttr, yVal), actual, sum_) = probs['%s-%s' % (data.domain[self.attrX].values[i], data.domain[self.attrY].values[j])] expected = float(xVal * yVal) / float(sum_) if expected == 0: continue pearson2 = (actual - expected) * (actual - expected) / expected chisquare += pearson2 ###################### # draw rectangles currX = xOff max_ylabel_w = 0 normX, normY = sum(valsX), sum(valsY) for i in range(len(valsX)): if valsX[i] == 0: continue currY = yOff width = int(float(sqareSize * valsX[i]) / float(normX)) #for j in range(len(valsY)): for j in range(len(valsY) - 1, -1, -1): # this way we sort y values correctly ((xAttr, xVal), (yAttr, yVal), actual, sum_) = probs['%s-%s' % (data.domain[self.attrX].values[i], data.domain[self.attrY].values[j])] if valsY[j] == 0: continue height = int(float(sqareSize * valsY[j]) / float(normY)) # create rectangle rect = OWCanvasRectangle(self.canvas, currX + 2, currY + 2, width - 4, height - 4, z=-10) self.addRectIndependencePearson(rect, currX + 2, currY + 2, width - 4, height - 4, (xAttr, xVal), (yAttr, yVal), actual, sum_) expected = float(xVal * yVal) / float(sum_) pearson = (actual - expected) / sqrt(expected) tooltipText = """<b>X Attribute: %s</b><br>Value: <b>%s</b><br>Number of examples (p(x)): <b>%d (%.2f%%)</b><hr> <b>Y Attribute: %s</b><br>Value: <b>%s</b><br>Number of examples (p(y)): <b>%d (%.2f%%)</b><hr> <b>Number Of Examples (Probabilities):</b><br>Expected (p(x)p(y)): <b>%.1f (%.2f%%)</b><br>Actual (p(x,y)): <b>%d (%.2f%%)</b> <hr><b>Statistics:</b><br>Chi-square: <b>%.2f</b><br>Standardized Pearson residual: <b>%.2f</b>""" % ( self.attrX, getHtmlCompatibleString(xAttr), xVal, 100.0 * float(xVal) / float(sum_), self.attrY, getHtmlCompatibleString(yAttr), yVal, 100.0 * float(yVal) / float(sum_), expected, 100.0 * float(xVal * yVal) / float(sum_ * sum_), actual, 100.0 * float(actual) / float(sum_), chisquare, pearson) rect.setToolTip(tooltipText) currY += height if currX == xOff: xl = OWCanvasText(self.canvas, "", xOff - 10, currY - height / 2, Qt.AlignRight | Qt.AlignVCenter, htmlText=getHtmlCompatibleString( data.domain[self.attrY].values[j])) max_ylabel_w = max(int(xl.boundingRect().width()), max_ylabel_w) OWCanvasText(self.canvas, "", currX + width / 2, yOff + sqareSize + 5, Qt.AlignCenter, htmlText=getHtmlCompatibleString( data.domain[self.attrX].values[i])) currX += width # show attribute names OWCanvasText(self.canvas, self.attrY, max(xOff - 20 - max_ylabel_w, 20), yOff + sqareSize / 2, Qt.AlignRight | Qt.AlignVCenter, bold=1, vertical=True) OWCanvasText(self.canvas, self.attrX, xOff + sqareSize / 2, yOff + sqareSize + 15, Qt.AlignCenter, bold=1) #self.canvas.update() # create a dictionary with all possible pairs of "combination-of-attr-values" : count def getConditionalDistributions(self, data, attrs): cond_dist = defaultdict(int) all_attrs = [data.domain[a] for a in attrs] if data.domain.class_var is not None: all_attrs.append(data.domain.class_var) for i in range(1, len(all_attrs) + 1): attr = all_attrs[:i] if type(data) == SqlTable: # make all possible pairs of attributes + class_var attr = [a.to_sql() for a in attr] fields = attr + ["COUNT(*)"] query = data._sql_query(fields, group_by=attr) with data._execute_sql_query(query) as cur: res = cur.fetchall() for r in res: str_values = [ a.repr_val(a.to_val(x)) for a, x in zip(all_attrs, r[:-1]) ] str_values = [ x if x != '?' else 'None' for x in str_values ] cond_dist['-'.join(str_values)] = r[-1] else: for indices in product(*(range(len(a.values)) for a in attr)): vals = [] conditions = [] for k, ind in enumerate(indices): vals.append(attr[k].values[ind]) fd = Orange.data.filter.FilterDiscrete( column=attr[k], values=[attr[k].values[ind]]) conditions.append(fd) filt = Orange.data.filter.Values(conditions) filtdata = filt(data) cond_dist['-'.join(vals)] = len(filtdata) return cond_dist ###################################################################### ## show deviations from attribute independence with standardized pearson residuals def addRectIndependencePearson(self, rect, x, y, w, h, xAttr_xVal, yAttr_yVal, actual, sum): xAttr, xVal = xAttr_xVal yAttr, yVal = yAttr_yVal expected = float(xVal * yVal) / float(sum) pearson = (actual - expected) / sqrt(expected) if pearson > 0: # if there are more examples that we would expect under the null hypothesis intPearson = floor(pearson) pen = QPen(QColor(0, 0, 255), 1) rect.setPen(pen) b = 255 r = g = 255 - intPearson * 20 r = g = max(r, 55) # elif pearson < 0: intPearson = ceil(pearson) pen = QPen(QColor(255, 0, 0), 1) rect.setPen(pen) r = 255 b = g = 255 + intPearson * 20 b = g = max(b, 55) else: pen = QPen(QColor(255, 255, 255), 1) r = g = b = 255 # white color = QColor(r, g, b) brush = QBrush(color) rect.setBrush(brush) if self.showCases and w > 6 and h > 6: if self.showInColor: if pearson > 0: c = QColor(0, 0, 255) else: c = QColor(255, 0, 0) else: c = Qt.black for i in range(int(actual)): OWCanvasEllipse(self.canvas, random.randint(x + 1, x + w - 4), random.randint(y + 1, y + h - 4), 3, 3, penColor=c, brushColor=c, z=100) if pearson > 0: pearson = min(pearson, 10) kvoc = 1 - 0.08 * pearson # if pearson in [0..10] --> kvoc in [1..0.2] else: pearson = max(pearson, -10) kvoc = 1 - 0.4 * pearson self.addLines(x, y, w, h, kvoc, pen) ################################################## # add lines def addLines(self, x, y, w, h, diff, pen): if not self.showLines: return if w == 0 or h == 0: return # create lines dist = 20 # original distance between two lines in pixels dist = dist * diff temp = dist while (temp < w): OWCanvasLine(self.canvas, temp + x, y, temp + x, y + h, 1, pen.color()) temp += dist temp = dist while (temp < h): OWCanvasLine(self.canvas, x, y + temp, x + w, y + temp, 1, pen.color()) temp += dist def saveToFileCanvas(self): sizeDlg = OWChooseImageSizeDlg(self.canvas, parent=self) sizeDlg.exec_() def closeEvent(self, ce): # self.optimizationDlg.hide() QDialog.closeEvent(self, ce)
class OWSieveDiagram(OWWidget): name = "Sieve Diagram" icon = "icons/SieveDiagram.svg" priority = 4200 inputs = [("Data", Table, "setData", Default), ("Features", AttributeList, "setShownAttributes")] outputs = [] settingsList = ["showLines", "showCases", "showInColor"] def __init__(self,parent=None, signalManager = None): OWWidget.__init__(self, parent, signalManager, "Sieve diagram", True) #self.controlArea.setMinimumWidth(250) #set default settings self.data = None self.attrX = "" self.attrY = "" self.attrCondition = "" self.attrConditionValue = "" self.showLines = 1 self.showCases = 0 self.showInColor = 1 self.attributeSelectionList = None self.stopCalculating = 0 self.canvas = QGraphicsScene() self.canvasView = QGraphicsView(self.canvas, self.mainArea) self.mainArea.layout().addWidget(self.canvasView) self.canvasView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.canvasView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) #GUI self.attrSelGroup = gui.widgetBox(self.controlArea, box = "Shown attributes") self.attrXCombo = gui.comboBox(self.attrSelGroup, self, value="attrX", label="X attribute:", orientation="horizontal", tooltip = "Select an attribute to be shown on the X axis", callback = self.updateGraph, sendSelectedValue = 1, valueType = str, labelWidth = 70) self.attrYCombo = gui.comboBox(self.attrSelGroup, self, value="attrY", label="Y attribute:", orientation="horizontal", tooltip = "Select an attribute to be shown on the Y axis", callback = self.updateGraph, sendSelectedValue = 1, valueType = str, labelWidth = 70) gui.separator(self.controlArea) self.conditionGroup = gui.widgetBox(self.controlArea, box = "Condition") self.attrConditionCombo = gui.comboBox(self.conditionGroup, self, value="attrCondition", label="Attribute:", orientation="horizontal", callback = self.updateConditionAttr, sendSelectedValue = 1, valueType = str, labelWidth = 70) self.attrConditionValueCombo = gui.comboBox(self.conditionGroup, self, value="attrConditionValue", label="Value:", orientation="horizontal", callback = self.updateGraph, sendSelectedValue = 1, valueType = str, labelWidth = 70) gui.separator(self.controlArea) box2 = gui.widgetBox(self.controlArea, box = "Visual settings") gui.checkBox(box2, self, "showLines", "Show lines", callback = self.updateGraph) hbox = gui.widgetBox(box2, orientation = "horizontal") gui.checkBox(hbox, self, "showCases", "Show data examples...", callback = self.updateGraph) gui.checkBox(hbox, self, "showInColor", "...in color", callback = self.updateGraph) gui.separator(self.controlArea) # self.optimizationDlg = OWSieveOptimization(self, self.signalManager) # optimizationButtons = gui.widgetBox(self.controlArea, "Dialogs", orientation = "horizontal") # gui.button(optimizationButtons, self, "VizRank", callback = self.optimizationDlg.reshow, debuggingEnabled = 0, tooltip = "Find attribute groups with highest value dependency") gui.rubber(self.controlArea) # self.wdChildDialogs = [self.optimizationDlg] # used when running widget debugging # self.graphButton.clicked.connect(self.saveToFileCanvas) self.icons = gui.attributeIconDict self.resize(800, 550) random.seed() def sendReport(self): self.startReport("%s [%s, %s]" % (self.windowTitle(), self.attrX, self.attrY)) self.reportSettings("", [("X-Attribute", self.attrX), ("Y-Attribute", self.attrY), self.attrCondition != "(None)" and ("Condition", "%s = '%s'" % (self.attrCondition, self.attrConditionValue))]) # self.reportImage(lambda *x: OWChooseImageSizeDlg(self.canvas).saveImage(*x)) # receive new data and update all fields def setData(self, data): if type(data) == SqlTable and data.approx_len() > LARGE_TABLE: data = data.sample_time(DEFAULT_SAMPLE_TIME) self.information(0) self.information(1) sameDomain = self.data and data and self.data.domain.checksum() == data.domain.checksum() # preserve attribute choice if the domain is the same # self.data = self.optimizationDlg.setData(data, 0) self.data = data self.warning(0, "") if data: if any(isinstance(attr, ContinuousVariable) for attr in data.domain): self.warning(0, "Data contains continuous variables. " + "Discretize the data to use them.") if not sameDomain: self.initCombos() self.setShownAttributes(self.attributeSelectionList) ## Attribute selection signal def setShownAttributes(self, attrList): self.attributeSelectionList = attrList if self.data and self.attributeSelectionList and len(attrList) >= 2: attrs = [attr.name for attr in self.data.domain] if attrList[0] in attrs and attrList[1] in attrs: self.attrX = attrList[0] self.attrY = attrList[1] self.updateGraph() # create data subset depending on conditional attribute and value def getConditionalData(self, xAttr = None, yAttr = None, dropMissingData = 1): if not self.data: return None if not xAttr: xAttr = self.attrX if not yAttr: yAttr = self.attrY if not (xAttr and yAttr): return if self.attrCondition == "(None)": data = self.data[:, [xAttr, yAttr]] # data = self.data.select([xAttr, yAttr]) else: # data = orange.Preprocessor_dropMissing(self.data.select([xAttr, yAttr, self.attrCondition])) # data = self.data.select({self.attrCondition:self.attrConditionValue}) fd = Orange.data.filter.FilterDiscrete(column=self.attrCondition, values=[self.attrConditionValue]) filt = Orange.data.filter.Values([fd]) filt.domain = self.data.domain data = filt(self.data) # if dropMissingData: return orange.Preprocessor_dropMissing(data) #else: return data # new conditional attribute was set - update graph def updateConditionAttr(self): self.attrConditionValueCombo.clear() if self.attrCondition != "(None)": for val in self.data.domain[self.attrCondition].values: self.attrConditionValueCombo.addItem(val) self.attrConditionValue = str(self.attrConditionValueCombo.itemText(0)) self.updateGraph() # initialize lists for shown and hidden attributes def initCombos(self): self.attrXCombo.clear() self.attrYCombo.clear() self.attrConditionCombo.clear() self.attrConditionCombo.addItem("(None)") self.attrConditionValueCombo.clear() if not self.data: return for i, var in enumerate(self.data.domain): if isinstance(var, DiscreteVariable): self.attrXCombo.addItem(self.icons[self.data.domain[i]], self.data.domain[i].name) self.attrYCombo.addItem(self.icons[self.data.domain[i]], self.data.domain[i].name) self.attrConditionCombo.addItem(self.icons[self.data.domain[i]], self.data.domain[i].name) self.attrCondition = str(self.attrConditionCombo.itemText(0)) if self.attrXCombo.count() > 0: self.attrX = str(self.attrXCombo.itemText(0)) self.attrY = str(self.attrYCombo.itemText(self.attrYCombo.count() > 1)) def resizeEvent(self, e): OWWidget.resizeEvent(self,e) self.updateGraph() def showEvent(self, ev): OWWidget.showEvent(self, ev) self.updateGraph() ## updateGraph - gets called every time the graph has to be updated def updateGraph(self, *args): for item in self.canvas.items(): self.canvas.removeItem(item) # remove all canvas items if not self.data: return if not self.attrX or not self.attrY: return data = self.getConditionalData() if not data or len(data) == 0: return valsX = [] valsY = [] # contX = orange.ContingencyAttrAttr(self.attrX, self.attrX, data) # distribution of X attribute # contY = orange.ContingencyAttrAttr(self.attrY, self.attrY, data) # distribution of Y attribute contX = get_contingency(data, self.attrX, self.attrX) contY = get_contingency(data, self.attrY, self.attrY) # compute contingency of x and y attributes for entry in contX: sum_ = 0 try: for val in entry: sum_ += val except: pass valsX.append(sum_) for entry in contY: sum_ = 0 try: for val in entry: sum_ += val except: pass valsY.append(sum_) # create cartesian product of selected attributes and compute contingency # (cart, profit) = FeatureByCartesianProduct(data, [data.domain[self.attrX], data.domain[self.attrY]]) # tempData = data.select(list(data.domain) + [cart]) # contXY = orange.ContingencyAttrAttr(cart, cart, tempData) # distribution of X attribute # contXY = get_contingency(tempData, cart, cart) contXY = self.getConditionalDistributions(data, [data.domain[self.attrX], data.domain[self.attrY]]) # compute probabilities probs = {} for i in range(len(valsX)): valx = valsX[i] for j in range(len(valsY)): valy = valsY[j] actualProb = 0 try: actualProb = contXY['%s-%s' %(data.domain[self.attrX].values[i], data.domain[self.attrY].values[j])] # for val in contXY['%s-%s' %(i, j)]: actualProb += val except: actualProb = 0 probs['%s-%s' %(data.domain[self.attrX].values[i], data.domain[self.attrY].values[j])] = ((data.domain[self.attrX].values[i], valx), (data.domain[self.attrY].values[j], valy), actualProb, len(data)) # get text width of Y attribute name text = OWCanvasText(self.canvas, data.domain[self.attrY].name, x = 0, y = 0, bold = 1, show = 0, vertical=True) xOff = int(text.boundingRect().height() + 40) yOff = 50 sqareSize = min(self.canvasView.width() - xOff - 35, self.canvasView.height() - yOff - 30) if sqareSize < 0: return # canvas is too small to draw rectangles self.canvasView.setSceneRect(0, 0, self.canvasView.width(), self.canvasView.height()) # print graph name if self.attrCondition == "(None)": name = "<b>P(%s, %s) ≠ P(%s)×P(%s)</b>" %(self.attrX, self.attrY, self.attrX, self.attrY) else: name = "<b>P(%s, %s | %s = %s) ≠ P(%s | %s = %s)×P(%s | %s = %s)</b>" %(self.attrX, self.attrY, self.attrCondition, getHtmlCompatibleString(self.attrConditionValue), self.attrX, self.attrCondition, getHtmlCompatibleString(self.attrConditionValue), self.attrY, self.attrCondition, getHtmlCompatibleString(self.attrConditionValue)) OWCanvasText(self.canvas, "" , xOff+ sqareSize/2, 20, Qt.AlignCenter, htmlText = name) OWCanvasText(self.canvas, "N = " + str(len(data)), xOff+ sqareSize/2, 38, Qt.AlignCenter, bold = 0) ###################### # compute chi-square chisquare = 0.0 for i in range(len(valsX)): for j in range(len(valsY)): ((xAttr, xVal), (yAttr, yVal), actual, sum_) = probs['%s-%s' %(data.domain[self.attrX].values[i], data.domain[self.attrY].values[j])] expected = float(xVal*yVal)/float(sum_) if expected == 0: continue pearson2 = (actual - expected)*(actual - expected) / expected chisquare += pearson2 ###################### # draw rectangles currX = xOff max_ylabel_w = 0 normX, normY = sum(valsX), sum(valsY) for i in range(len(valsX)): if valsX[i] == 0: continue currY = yOff width = int(float(sqareSize * valsX[i])/float(normX)) #for j in range(len(valsY)): for j in range(len(valsY)-1, -1, -1): # this way we sort y values correctly ((xAttr, xVal), (yAttr, yVal), actual, sum_) = probs['%s-%s' %(data.domain[self.attrX].values[i], data.domain[self.attrY].values[j])] if valsY[j] == 0: continue height = int(float(sqareSize * valsY[j])/float(normY)) # create rectangle rect = OWCanvasRectangle(self.canvas, currX+2, currY+2, width-4, height-4, z = -10) self.addRectIndependencePearson(rect, currX+2, currY+2, width-4, height-4, (xAttr, xVal), (yAttr, yVal), actual, sum_) expected = float(xVal*yVal)/float(sum_) pearson = (actual - expected) / sqrt(expected) tooltipText = """<b>X Attribute: %s</b><br>Value: <b>%s</b><br>Number of examples (p(x)): <b>%d (%.2f%%)</b><hr> <b>Y Attribute: %s</b><br>Value: <b>%s</b><br>Number of examples (p(y)): <b>%d (%.2f%%)</b><hr> <b>Number Of Examples (Probabilities):</b><br>Expected (p(x)p(y)): <b>%.1f (%.2f%%)</b><br>Actual (p(x,y)): <b>%d (%.2f%%)</b> <hr><b>Statistics:</b><br>Chi-square: <b>%.2f</b><br>Standardized Pearson residual: <b>%.2f</b>""" %(self.attrX, getHtmlCompatibleString(xAttr), xVal, 100.0*float(xVal)/float(sum_), self.attrY, getHtmlCompatibleString(yAttr), yVal, 100.0*float(yVal)/float(sum_), expected, 100.0*float(xVal*yVal)/float(sum_*sum_), actual, 100.0*float(actual)/float(sum_), chisquare, pearson ) rect.setToolTip(tooltipText) currY += height if currX == xOff: xl = OWCanvasText(self.canvas, "", xOff - 10, currY - height/2, Qt.AlignRight | Qt.AlignVCenter, htmlText = getHtmlCompatibleString(data.domain[self.attrY].values[j])) max_ylabel_w = max(int(xl.boundingRect().width()), max_ylabel_w) OWCanvasText(self.canvas, "", currX + width/2, yOff + sqareSize + 5, Qt.AlignCenter, htmlText = getHtmlCompatibleString(data.domain[self.attrX].values[i])) currX += width # show attribute names OWCanvasText(self.canvas, self.attrY, max(xOff-20-max_ylabel_w, 20), yOff + sqareSize/2, Qt.AlignRight | Qt.AlignVCenter, bold = 1, vertical=True) OWCanvasText(self.canvas, self.attrX, xOff + sqareSize/2, yOff + sqareSize + 15, Qt.AlignCenter, bold = 1) #self.canvas.update() # create a dictionary with all possible pairs of "combination-of-attr-values" : count def getConditionalDistributions(self, data, attrs): cond_dist = defaultdict(int) all_attrs = [data.domain[a] for a in attrs] if data.domain.class_var is not None: all_attrs.append(data.domain.class_var) for i in range(1, len(all_attrs) + 1): attr = all_attrs[:i] if type(data) == SqlTable: # make all possible pairs of attributes + class_var attr = [a.to_sql() for a in attr] fields = attr + ["COUNT(*)"] query = data._sql_query(fields, group_by=attr) with data._execute_sql_query(query) as cur: res = cur.fetchall() for r in res: str_values =[a.repr_val(a.to_val(x)) for a, x in zip(all_attrs, r[:-1])] str_values = [x if x != '?' else 'None' for x in str_values] cond_dist['-'.join(str_values)] = r[-1] else: for indices in product(*(range(len(a.values)) for a in attr)): vals = [] conditions = [] for k, ind in enumerate(indices): vals.append(attr[k].values[ind]) fd = Orange.data.filter.FilterDiscrete(column=attr[k], values=[attr[k].values[ind]]) conditions.append(fd) filt = Orange.data.filter.Values(conditions) filtdata = filt(data) cond_dist['-'.join(vals)] = len(filtdata) return cond_dist ###################################################################### ## show deviations from attribute independence with standardized pearson residuals def addRectIndependencePearson(self, rect, x, y, w, h, xAttr_xVal, yAttr_yVal, actual, sum): xAttr, xVal = xAttr_xVal yAttr, yVal = yAttr_yVal expected = float(xVal*yVal)/float(sum) pearson = (actual - expected) / sqrt(expected) if pearson > 0: # if there are more examples that we would expect under the null hypothesis intPearson = floor(pearson) pen = QPen(QColor(0,0,255), 1); rect.setPen(pen) b = 255 r = g = 255 - intPearson*20 r = g = max(r, 55) # elif pearson < 0: intPearson = ceil(pearson) pen = QPen(QColor(255,0,0), 1) rect.setPen(pen) r = 255 b = g = 255 + intPearson*20 b = g = max(b, 55) else: pen = QPen(QColor(255,255,255), 1) r = g = b = 255 # white color = QColor(r,g,b) brush = QBrush(color); rect.setBrush(brush) if self.showCases and w > 6 and h > 6: if self.showInColor: if pearson > 0: c = QColor(0,0,255) else: c = QColor(255, 0,0) else: c = Qt.black for i in range(int(actual)): OWCanvasEllipse(self.canvas, random.randint(x+1, x + w-4), random.randint(y+1, y + h-4), 3, 3, penColor = c, brushColor = c, z = 100) if pearson > 0: pearson = min(pearson, 10) kvoc = 1 - 0.08 * pearson # if pearson in [0..10] --> kvoc in [1..0.2] else: pearson = max(pearson, -10) kvoc = 1 - 0.4*pearson self.addLines(x,y,w,h, kvoc, pen) ################################################## # add lines def addLines(self, x,y,w,h, diff, pen): if not self.showLines: return if w == 0 or h == 0: return # create lines dist = 20 # original distance between two lines in pixels dist = dist * diff temp = dist while (temp < w): OWCanvasLine(self.canvas, temp+x, y, temp+x, y+h, 1, pen.color()) temp += dist temp = dist while (temp < h): OWCanvasLine(self.canvas, x, y+temp, x+w, y+temp, 1, pen.color()) temp += dist def saveToFileCanvas(self): sizeDlg = OWChooseImageSizeDlg(self.canvas, parent=self) sizeDlg.exec_() def closeEvent(self, ce): # self.optimizationDlg.hide() QDialog.closeEvent(self, ce)
class NostraIllustCheckDlg(QtGui.QDialog, FORM_CLASS): def __init__(self, theCanvas, theLayer, selFeatureIds, parent=None): super(NostraIllustCheckDlg, self).__init__(parent) self.setupUi(self) self.mTheCanvas = theCanvas self.mTheLayer = theLayer self.mSelFeatureIds = selFeatureIds self.mAllFeatureIds = [] self.highlightList = [] uri = QgsDataSourceURI(self.mTheLayer.source()) self.conn = psycopg2.connect('''host='%s' dbname='%s' user='******' password='******' ''' %\ (uri.host(), uri.database(), uri.username(), uri.password())) self.pg = self.conn.cursor() # members shown in graphic view. self.mScene = QGraphicsScene() self.graphicsViewShowImage.setScene(self.mScene) self.mPixmapList = [] featureIter = self.mTheLayer.getFeatures(QgsFeatureRequest().setFlags( QgsFeatureRequest.NoGeometry)) inti = 0 theFeature = QgsFeature() while (featureIter.nextFeature(theFeature)): inti += 1 self.mAllFeatureIds.append(theFeature.id()) self.spinBoxFeatureIndex.setValue(1) self.spinBoxFeatureIndex.setMinimum(1) self.spinBoxFeatureIndex.setMaximum(inti) errMsg = [''] self.initComboBoxOutlinkid() self.selectFeatureById(errMsg, self.mSelFeatureIds[0], bZoomToSelected=False) self.comboBoxOutlinkid.setFocus() self.pushButtonPrev.clicked.connect(self.onPushButtonPrev) self.pushButtonNext.clicked.connect(self.onPushButtonNext) self.connect(self.comboBoxOutlinkid, QtCore.SIGNAL('activated(QString)'), self.comboBoxOutlinkidChanged) def resizeEvent(self, event): self.showImageInGraphicsView() return def disableAllControls(self): self.pushButtonPrev.setEnabled(False) self.pushButtonPrev.setEnabled(False) self.comboBoxOutlinkid.setEnabled(False) self.textEditFeatureInfo.setEnabled(False) return def initComboBoxOutlinkid(self): while (self.comboBoxOutlinkid.count() > 0): self.comboBoxOutlinkid.removeItem(0) for oneFeatureId in self.mSelFeatureIds: featureIter = self.mTheLayer.getFeatures( QgsFeatureRequest(oneFeatureId).setFlags( QgsFeatureRequest.NoGeometry)) theFeature = QgsFeature() if featureIter.nextFeature(theFeature) == False: return if self.mIsMyFeature(theFeature): strTemp = "%.0f, %.0f" % (theFeature.attribute('arc1'), theFeature.attribute('arc2')) self.comboBoxOutlinkid.addItem(strTemp) def showFeatureDetail(self, errMsg, theFeature): strFeatureInfo = self.getFeatureInfoString(theFeature) self.textEditFeatureInfo.setText(strFeatureInfo) if self.mIsMyFeature(theFeature) == False: return errMsg = [''] day_pic = theFeature.attribute('day_pic') arrowimg = theFeature.attribute('arrowimg') basePath = r"\\tangpinghui\20151010_nostra_junctionview\output_jpg_and_png" day_pic_path = os.path.join(basePath, day_pic + ".psd\Main_Day.jpg") arrowimg_path = '' tempPath = os.path.join(basePath, day_pic + ".psd") for curDir, subDirs, fileNames in os.walk(tempPath): for oneFile in fileNames: if oneFile.find(arrowimg) != -1 and oneFile.find('_en') != -1: arrowimg_path = os.path.join(curDir, oneFile) self.mPixmapList = [] patternPixmap = QPixmap() patternPixmap.load(day_pic_path) self.mPixmapList.append(patternPixmap) arrowPixmap = QPixmap() arrowPixmap.load(arrowimg_path) self.mPixmapList.append(arrowPixmap) self.showImageInGraphicsView() return def showImageInGraphicsView(self): # remove all items in member QGraphicsScene. for oneItem in self.mScene.items(): self.mScene.removeItem(oneItem) for onePixmap in self.mPixmapList: self.mScene.addPixmap( self.getPixMapSizedByWidgt(onePixmap, self.graphicsViewShowImage)) self.mScene.setSceneRect(0, 0, self.graphicsViewShowImage.width() - 5, self.graphicsViewShowImage.height() - 5) return def getFeatureInfoString(self, theFeature): fieldList = theFeature.fields() attrList = theFeature.attributes() strFeatureInfo = "field count: %d\n" % len(fieldList) for oneField, oneAttr in zip(fieldList, attrList): if isinstance(oneAttr, float): strFeatureInfo += "%s: %.0f\n" % (oneField.name(), oneAttr) else: strFeatureInfo += "%s: %s\n" % (oneField.name(), oneAttr) return strFeatureInfo def comboBoxOutlinkidChanged(self, txt): errMsg = [''] inti = self.comboBoxOutlinkid.currentIndex() self.selectFeatureById(errMsg, self.mSelFeatureIds[inti], bZoomToSelected=False) if errMsg[0] <> '': QMessageBox.information(self, "Nostra Illust Check", """error:\n%s""" % errMsg[0]) return return def onPushButtonPrev(self): self.spinBoxFeatureIndex.setValue(self.spinBoxFeatureIndex.value() - 1) prevFeatureId = self.mAllFeatureIds[self.spinBoxFeatureIndex.value() - 1] errMsg = [''] self.selectFeatureById(errMsg, prevFeatureId) if errMsg[0] <> '': QMessageBox.information(self, "Nostra Illust Check", """error:\n%s""" % errMsg[0]) return return def onPushButtonNext(self): self.spinBoxFeatureIndex.setValue(self.spinBoxFeatureIndex.value() + 1) nextFeatureId = self.mAllFeatureIds[self.spinBoxFeatureIndex.value() - 1] errMsg = [''] self.selectFeatureById(errMsg, nextFeatureId) if errMsg[0] <> '': QMessageBox.information(self, "Nostra Illust Check", """error:\n%s""" % errMsg[0]) return return def selectFeatureById(self, errMsg, featureId, bZoomToSelected=True): self.mTheLayer.removeSelection() self.mTheLayer.select(featureId) theFeature = self.mTheLayer.selectedFeatures()[0] self.showFeatureDetail(errMsg, theFeature) if errMsg[0] <> '': return if bZoomToSelected == True: center = self.mTheCanvas.panToSelected(self.mTheLayer) self.mTheCanvas.refresh() self.clearHighlight() arc1 = "%.0f" % theFeature.attribute('arc1') sqlcmd = """ SET bytea_output TO escape; select st_asbinary(the_geom) from temp_topo_link where routeid=%s """ % arc1 self.pg.execute(sqlcmd) rows = self.pg.fetchall() for row in rows: qgsGeometry = QgsGeometry() qgsGeometry.fromWkb(str(row[0])) self.highlightByGeometry(qgsGeometry, QColor(249, 27, 15, 168)) arc2 = "%.0f" % theFeature.attribute('arc2') sqlcmd = """ SET bytea_output TO escape; select st_asbinary(the_geom) from temp_topo_link where routeid=%s """ % arc2 self.pg.execute(sqlcmd) rows = self.pg.fetchall() for row in rows: qgsGeometry = QgsGeometry() qgsGeometry.fromWkb(str(row[0])) self.highlightByGeometry(qgsGeometry, QColor(22, 151, 0, 168)) new_arc1 = "%.0f" % theFeature.attribute('new_arc1') if new_arc1 is not None and new_arc1 <> arc1: sqlcmd = """ SET bytea_output TO escape; select st_asbinary(the_geom) from temp_topo_link where routeid=%s """ % new_arc1 self.pg.execute(sqlcmd) rows = self.pg.fetchall() for row in rows: qgsGeometry = QgsGeometry() qgsGeometry.fromWkb(str(row[0])) self.highlightByGeometry(qgsGeometry, QColor(253, 158, 153, 128)) new_arc2 = "%.0f" % theFeature.attribute('new_arc2') if new_arc2 is not None and new_arc2 <> arc2: sqlcmd = """ SET bytea_output TO escape; select st_asbinary(the_geom) from temp_topo_link where routeid=%s """ % new_arc2 self.pg.execute(sqlcmd) rows = self.pg.fetchall() for row in rows: qgsGeometry = QgsGeometry() qgsGeometry.fromWkb(str(row[0])) self.highlightByGeometry(qgsGeometry, QColor(147, 255, 155, 128)) return def getPixMapSizedByWidgt(self, pixmap, theWidgt): pixmapWidth = pixmap.width() pixmapHeight = pixmap.height() gpViewWidth = theWidgt.width() gpViewHeight = theWidgt.height() destWidth = 0 destHeight = 0 if pixmapWidth > gpViewWidth - 5: destWidth = gpViewWidth - 5 else: destWidth = pixmapWidth if pixmapHeight > gpViewHeight - 5: destHeight = gpViewHeight - 5 else: destHeight = pixmapHeight return pixmap.scaled(destWidth, destHeight, QtCore.Qt.IgnoreAspectRatio, QtCore.Qt.SmoothTransformation) def mIsMyFeature(self, theFeature): return NostraIllustCheckDlg.isMyFeature(theFeature) @staticmethod def isMyFeature(theFeature): try: gid = theFeature.attribute('gid') arc1 = theFeature.attribute('arc1') arc2 = theFeature.attribute('arc2') day_pic = theFeature.attribute('day_pic') night_pic = theFeature.attribute('night_pic') arrowimg = theFeature.attribute('arrowimg') lon = theFeature.attribute('lon') lat = theFeature.attribute('lat') new_arc1 = theFeature.attribute('new_arc1') new_arc2 = theFeature.attribute('new_arc2') except KeyError, kErr: return False except Exception, ex: return False
class PaintArea(QGraphicsView): def __init__(self, parent): QGraphicsView.__init__(self) self.init(parent) self.initShape() self.initCall = 2 self.minimumWidth = 550 self.setEnabled(False) def init(self, parent): self.timeline = parent self.pixmap = None self.image = None def initShape(self): self.scene = QGraphicsScene() self.setScene(self.scene) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setAlignment(Qt.AlignLeft) self.clicStart = None self.selectionRect = None def resizeEvent(self, sizEvent): if self.selectionRect and self.scene: self.scene.removeItem(self.selectionRect) self.selectionRect = None self.clicStart = None self.width = sizEvent.size().width() self.height = sizEvent.size().height() if self.initCall > 0: self.timeline.options.setMaximumWidth(self.timeline.options.minimumSizeHint().width()) self.initCall -= 1 if self.width < self.minimumWidth: self.timeline.setStateInfo('Unable to draw - Not enought width') else: self.timeline.workerThread.render() def mousePressEvent(self, mouseEvent): self.clicStart = mouseEvent.pos() if self.clicStart.x() < self.timeline.draw.yLeftMargin - 1: self.clicStart.setX(self.timeline.draw.yLeftMargin - 1) if self.clicStart.x() > self.timeline.ploter.width - self.timeline.m + 1: self.clicStart.setX(self.timeline.ploter.width - self.timeline.m + 1) if self.selectionRect: for item in self.scene.items(): if str(type(item)) == "<class 'PyQt4.QtGui.QGraphicsRectItem'>": self.scene.removeItem(item) self.selectionRect = None self.timeline.options.zoomButton.setEnabled(False) self.timeline.options.exportButton.setEnabled(False) self.timeline.options.selectedNodes.setText('Nothing selected') if self.timeline.selDateMin: self.timeline.options.selStartTime.setText('From ' + str(self.timeline.fromUSec(self.timeline.baseDateMin).strftime('%d.%m.%Y %H:%M:%S'))) self.timeline.options.selEndTime.setText('To ' + str(self.timeline.fromUSec(self.timeline.baseDateMax).strftime('%d.%m.%Y %H:%M:%S'))) else: self.timeline.options.selStartTime.setText('No selection start time') self.timeline.options.selEndTime.setText('No selection end time') self.timeline.options.selectedNodes.setText('Nothing selected') def mouseMoveEvent(self, dragMoveEvent): if self.clicStart: if self.clicStart.x() < dragMoveEvent.x(): x = self.clicStart.x() w = dragMoveEvent.x() - self.clicStart.x() else: x = dragMoveEvent.x() w = self.clicStart.x() - dragMoveEvent.x() # Limit rectangle to selectable area if x < self.timeline.draw.yLeftMargin - 1: x = self.timeline.draw.yLeftMargin - 1 w = self.clicStart.x() - x if x > self.timeline.ploter.width - self.timeline.m + 1: x = self.timeline.ploter.width - self.timeline.m + 1 w = 0 if x + w > self.timeline.ploter.width - self.timeline.m + 1: w = ((self.timeline.ploter.width - self.timeline.m + 1) - x) y = (self.timeline.m / 3) - 1 h = self.height - self.timeline.m - self.timeline.m / 3 + 2 if self.selectionRect and self.scene: self.scene.removeItem(self.selectionRect) self.selectionRect = self.scene.addRect(QRectF(x, y, w, h), QPen(Qt.DashDotDotLine)) def mouseReleaseEvent(self, mouseEvent): if self.selectionRect: if self.clicStart.x() > mouseEvent.x(): x1 = mouseEvent.x() x2 = self.clicStart.x() else: x1 = self.clicStart.x() x2 = mouseEvent.x() self.timeline.nodesInRange(x1, x2) start = self.timeline.draw.findXTime(x1) if start: self.timeline.options.selStartTime.setText('From ' + str(start.strftime('%d.%m.%Y %H:%M:%S'))) end = self.timeline.draw.findXTime(x2) if end: self.timeline.options.selEndTime.setText('To ' + str(end.strftime('%d.%m.%Y %H:%M:%S')))
class ImagesPreviewer(foundations.ui.common.QWidgetFactory(uiFile=UI_FILE)): """ | This class provides the Application images previewer. | It defines methods to navigate through the list of given images ( List of images paths ), zoom in / out and fit the displayed image, etc... """ def __init__(self, parent, paths=None, *args, **kwargs): """ This method initializes the class. :param parent: Object parent. ( QObject ) :param paths: Images paths. ( Tuple / List ) :param \*args: Arguments. ( \* ) :param \*\*kwargs: Keywords arguments. ( \*\* ) """ LOGGER.debug("> Initializing '{0}()' class.".format( self.__class__.__name__)) super(ImagesPreviewer, self).__init__(parent, *args, **kwargs) # --- Setting class attributes. --- self.__container = parent self.__paths = None self.paths = paths self.__uiResourcesDirectory = "resources" self.__uiResourcesDirectory = os.path.join(os.path.dirname(__file__), self.__uiResourcesDirectory) self.__uiPreviousImage = "Previous.png" self.__uiNextImage = "Next.png" self.__uiZoomOutImage = "Zoom_Out.png" self.__uiZoomInImage = "Zoom_In.png" # Ensure the ui object is destroyed on close to avoid memory leaks. self.setAttribute(Qt.WA_DeleteOnClose) self.__graphicsSceneBackgroundColor = QColor(32, 32, 32) self.__minimumZoomFactor = 0.05 self.__maximumZoomFactor = 25 self.__displayGraphicsItemMargin = 32 self.__graphicsSceneWidth = QApplication.desktop().screenGeometry( QApplication.desktop().primaryScreen()).width() * ( 1 / self.__minimumZoomFactor * 1.75) self.__graphicsSceneHeight = QApplication.desktop().screenGeometry( QApplication.desktop().primaryScreen()).height() * ( 1 / self.__minimumZoomFactor * 1.75) self.__wheelZoomFactor = 350.0 self.__keyZoomFactor = 1.20 self.__graphicsView = None self.__graphicsScene = None self.__displayGraphicsItem = None ImagesPreviewer.__initializeUi(self) self.loadImage() #****************************************************************************************************************** #*** Attributes properties. #****************************************************************************************************************** @property def container(self): """ This method is the property for **self.__container** attribute. :return: self.__container. ( QObject ) """ return self.__container @container.setter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def container(self, value): """ This method is the setter method for **self.__container** attribute. :param value: Attribute value. ( QObject ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format( self.__class__.__name__, "container")) @container.deleter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def container(self): """ This method is the deleter method for **self.__container** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format( self.__class__.__name__, "container")) @property def paths(self): """ This method is the property for **self.__paths** attribute. :return: self.__paths. ( Tuple / List ) """ return self.__paths @paths.setter @foundations.exceptions.handleExceptions(AssertionError) def paths(self, value): """ This method is the setter method for **self.__paths** attribute. :param value: Attribute value. ( Tuple / List ) """ if value is not None: assert type(value) in ( tuple, list ), "'{0}' attribute: '{1}' type is not 'tuple' or 'list'!".format( "paths", value) for element in value: assert type( element ) is unicode, "'{0}' attribute: '{1}' type is not 'unicode'!".format( "paths", element) self.__paths = value @paths.deleter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def paths(self): """ This method is the deleter method for **self.__paths** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format( self.__class__.__name__, "paths")) @property def uiResourcesDirectory(self): """ This method is the property for **self.__uiResourcesDirectory** attribute. :return: self.__uiResourcesDirectory. ( String ) """ return self.__uiResourcesDirectory @uiResourcesDirectory.setter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def uiResourcesDirectory(self, value): """ This method is the setter method for **self.__uiResourcesDirectory** attribute. :param value: Attribute value. ( String ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format( self.__class__.__name__, "uiResourcesDirectory")) @uiResourcesDirectory.deleter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def uiResourcesDirectory(self): """ This method is the deleter method for **self.__uiResourcesDirectory** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format( self.__class__.__name__, "uiResourcesDirectory")) @property def uiPreviousImage(self): """ This method is the property for **self.__uiPreviousImage** attribute. :return: self.__uiPreviousImage. ( String ) """ return self.__uiPreviousImage @uiPreviousImage.setter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def uiPreviousImage(self, value): """ This method is the setter method for **self.__uiPreviousImage** attribute. :param value: Attribute value. ( String ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format( self.__class__.__name__, "uiPreviousImage")) @uiPreviousImage.deleter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def uiPreviousImage(self): """ This method is the deleter method for **self.__uiPreviousImage** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format( self.__class__.__name__, "uiPreviousImage")) @property def uiNextImage(self): """ This method is the property for **self.__uiNextImage** attribute. :return: self.__uiNextImage. ( String ) """ return self.__uiNextImage @uiNextImage.setter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def uiNextImage(self, value): """ This method is the setter method for **self.__uiNextImage** attribute. :param value: Attribute value. ( String ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format( self.__class__.__name__, "uiNextImage")) @uiNextImage.deleter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def uiNextImage(self): """ This method is the deleter method for **self.__uiNextImage** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format( self.__class__.__name__, "uiNextImage")) @property def uiZoomOutImage(self): """ This method is the property for **self.__uiZoomOutImage** attribute. :return: self.__uiZoomOutImage. ( String ) """ return self.__uiZoomOutImage @uiZoomOutImage.setter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def uiZoomOutImage(self, value): """ This method is the setter method for **self.__uiZoomOutImage** attribute. :param value: Attribute value. ( String ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format( self.__class__.__name__, "uiZoomOutImage")) @uiZoomOutImage.deleter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def uiZoomOutImage(self): """ This method is the deleter method for **self.__uiZoomOutImage** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format( self.__class__.__name__, "uiZoomOutImage")) @property def uiZoomInImage(self): """ This method is the property for **self.__uiZoomInImage** attribute. :return: self.__uiZoomInImage. ( String ) """ return self.__uiZoomInImage @uiZoomInImage.setter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def uiZoomInImage(self, value): """ This method is the setter method for **self.__uiZoomInImage** attribute. :param value: Attribute value. ( String ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format( self.__class__.__name__, "uiZoomInImage")) @uiZoomInImage.deleter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def uiZoomInImage(self): """ This method is the deleter method for **self.__uiZoomInImage** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format( self.__class__.__name__, "uiZoomInImage")) @property def graphicsSceneBackgroundColor(self): """ This method is the property for **self.__graphicsSceneBackgroundColor** attribute. :return: self.__graphicsSceneBackgroundColor. ( QColors ) """ return self.__graphicsSceneBackgroundColor @graphicsSceneBackgroundColor.setter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def graphicsSceneBackgroundColor(self, value): """ This method is the setter method for **self.__graphicsSceneBackgroundColor** attribute. :param value: Attribute value. ( QColors ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format( self.__class__.__name__, "graphicsSceneBackgroundColor")) @graphicsSceneBackgroundColor.deleter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def graphicsSceneBackgroundColor(self): """ This method is the deleter method for **self.__graphicsSceneBackgroundColor** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format( self.__class__.__name__, "graphicsSceneBackgroundColor")) @property def graphicsSceneWidth(self): """ This method is the property for **self.__graphicsSceneWidth** attribute. :return: self.__graphicsSceneWidth. ( Integer ) """ return self.__graphicsSceneWidth @graphicsSceneWidth.setter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def graphicsSceneWidth(self, value): """ This method is the setter method for **self.__graphicsSceneWidth** attribute. :param value: Attribute value. ( Integer ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format( self.__class__.__name__, "graphicsSceneWidth")) @graphicsSceneWidth.deleter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def graphicsSceneWidth(self): """ This method is the deleter method for **self.__graphicsSceneWidth** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format( self.__class__.__name__, "graphicsSceneWidth")) @property def graphicsSceneHeight(self): """ This method is the property for **self.__graphicsSceneHeight** attribute. :return: self.__graphicsSceneHeight. ( Object ) """ return self.__graphicsSceneHeight @graphicsSceneHeight.setter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def graphicsSceneHeight(self, value): """ This method is the setter method for **self.__graphicsSceneHeight** attribute. :param value: Attribute value. ( Object ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format( self.__class__.__name__, "graphicsSceneHeight")) @graphicsSceneHeight.deleter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def graphicsSceneHeight(self): """ This method is the deleter method for **self.__graphicsSceneHeight** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format( self.__class__.__name__, "graphicsSceneHeight")) @property def minimumZoomFactor(self): """ This method is the property for **self.__minimumZoomFactor** attribute. :return: self.__minimumZoomFactor. ( Float ) """ return self.__minimumZoomFactor @minimumZoomFactor.setter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def minimumZoomFactor(self, value): """ This method is the setter method for **self.__minimumZoomFactor** attribute. :param value: Attribute value. ( Float ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format( self.__class__.__name__, "minimumZoomFactor")) @minimumZoomFactor.deleter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def minimumZoomFactor(self): """ This method is the deleter method for **self.__minimumZoomFactor** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format( self.__class__.__name__, "minimumZoomFactor")) @property def maximumZoomFactor(self): """ This method is the property for **self.__maximumZoomFactor** attribute. :return: self.__maximumZoomFactor. ( Float ) """ return self.__maximumZoomFactor @maximumZoomFactor.setter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def maximumZoomFactor(self, value): """ This method is the setter method for **self.__maximumZoomFactor** attribute. :param value: Attribute value. ( Float ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format( self.__class__.__name__, "maximumZoomFactor")) @maximumZoomFactor.deleter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def maximumZoomFactor(self): """ This method is the deleter method for **self.__maximumZoomFactor** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format( self.__class__.__name__, "maximumZoomFactor")) @property def wheelZoomFactor(self): """ This method is the property for **self.__wheelZoomFactor** attribute. :return: self.__wheelZoomFactor. ( Float ) """ return self.__wheelZoomFactor @wheelZoomFactor.setter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def wheelZoomFactor(self, value): """ This method is the setter method for **self.__wheelZoomFactor** attribute. :param value: Attribute value. ( Float ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format( self.__class__.__name__, "wheelZoomFactor")) @wheelZoomFactor.deleter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def wheelZoomFactor(self): """ This method is the deleter method for **self.__wheelZoomFactor** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format( self.__class__.__name__, "wheelZoomFactor")) @property def keyZoomFactor(self): """ This method is the property for **self.__keyZoomFactor** attribute. :return: self.__keyZoomFactor. ( Float ) """ return self.__keyZoomFactor @keyZoomFactor.setter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def keyZoomFactor(self, value): """ This method is the setter method for **self.__keyZoomFactor** attribute. :param value: Attribute value. ( Float ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format( self.__class__.__name__, "keyZoomFactor")) @keyZoomFactor.deleter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def keyZoomFactor(self): """ This method is the deleter method for **self.__keyZoomFactor** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format( self.__class__.__name__, "keyZoomFactor")) @property def graphicsView(self): """ This method is the property for **self.__graphicsView** attribute. :return: self.__graphicsView. ( QGraphicsView ) """ return self.__graphicsView @graphicsView.setter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def graphicsView(self, value): """ This method is the setter method for **self.__graphicsView** attribute. :param value: Attribute value. ( QGraphicsView ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format( self.__class__.__name__, "graphicsView")) @graphicsView.deleter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def graphicsView(self): """ This method is the deleter method for **self.__graphicsView** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format( self.__class__.__name__, "graphicsView")) @property def graphicsScene(self): """ This method is the property for **self.__graphicsScene** attribute. :return: self.__graphicsScene. ( QGraphicsScene ) """ return self.__graphicsScene @graphicsScene.setter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def graphicsScene(self, value): """ This method is the setter method for **self.__graphicsScene** attribute. :param value: Attribute value. ( QGraphicsScene ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format( self.__class__.__name__, "graphicsScene")) @graphicsScene.deleter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def graphicsScene(self): """ This method is the deleter method for **self.__graphicsScene** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format( self.__class__.__name__, "graphicsScene")) @property def displayGraphicsItem(self): """ This method is the property for **self.__displayGraphicsItem** attribute. :return: self.__displayGraphicsItem. ( QGraphicsItem ) """ return self.__displayGraphicsItem @displayGraphicsItem.setter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def displayGraphicsItem(self, value): """ This method is the setter method for **self.__displayGraphicsItem** attribute. :param value: Attribute value. ( QGraphicsItem ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format( self.__class__.__name__, "displayGraphicsItem")) @displayGraphicsItem.deleter @foundations.exceptions.handleExceptions( foundations.exceptions.ProgrammingError) def displayGraphicsItem(self): """ This method is the deleter method for **self.__displayGraphicsItem** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format( self.__class__.__name__, "displayGraphicsItem")) #****************************************************************************************************************** #*** Class methods. #****************************************************************************************************************** def show(self): """ This method reimplements the :meth:`QWidget.show` method. """ super(ImagesPreviewer, self).show() foundations.ui.common.centerWidgetOnScreen(self) def closeEvent(self, event): """ This method reimplements the :meth:`QWidget.closeEvent` method. :param event: QEvent ( QEvent ) """ LOGGER.debug( "> Removing '{0}' from Images Previewers list.".format(self)) self.__container.imagesPreviewers.remove(self) event.accept() def wheelEvent(self, event): """ This method reimplements the :meth:`QWidget.wheelEvent` method. :param event: QEvent ( QEvent ) """ self.scaleView(pow(1.5, event.delta() / self.__wheelZoomFactor)) def keyPressEvent(self, event): """ This method reimplements the :meth:`QWidget.keyPressEvent` method. :param event: QEvent ( QEvent ) """ key = event.key() if key == Qt.Key_Plus: self.scaleView(self.__keyZoomFactor) elif key == Qt.Key_Minus: self.scaleView(1 / self.__keyZoomFactor) else: super(ImagesPreviewer, self).keyPressEvent(event) def __initializeUi(self): """ This method initializes the Widget ui. """ LOGGER.debug("> Initializing '{0}' ui.".format( self.__class__.__name__)) self.Previous_Image_pushButton.setIcon( QIcon( os.path.join(self.__uiResourcesDirectory, self.__uiPreviousImage))) self.Next_Image_pushButton.setIcon( QIcon(os.path.join(self.__uiResourcesDirectory, self.__uiNextImage))) self.Zoom_In_pushButton.setIcon( QIcon( os.path.join(self.__uiResourcesDirectory, self.__uiZoomInImage))) self.Zoom_Out_pushButton.setIcon( QIcon( os.path.join(self.__uiResourcesDirectory, self.__uiZoomOutImage))) len(self.__paths) <= 1 and self.Navigation_frame.hide() LOGGER.debug("> Initializing graphics View.") self.__graphicsView = QGraphicsView() self.__graphicsView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.__graphicsView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.__graphicsView.setTransformationAnchor( QGraphicsView.AnchorUnderMouse) self.__graphicsView.setDragMode(QGraphicsView.ScrollHandDrag) # Reimplementing QGraphicsView wheelEvent method. self.__graphicsView.wheelEvent = self.wheelEvent LOGGER.debug("> Initializing graphics scene.") self.__graphicsScene = QGraphicsScene(self.__graphicsView) self.__graphicsScene.setItemIndexMethod(QGraphicsScene.NoIndex) self.__graphicsScene.setSceneRect( -(float(self.__graphicsSceneWidth)) / 2, -(float(self.__graphicsSceneHeight)) / 2, float(self.__graphicsSceneWidth), float(self.__graphicsSceneHeight)) self.__graphicsView.setScene(self.__graphicsScene) self.__graphicsView.setBackgroundBrush( QBrush(self.__graphicsSceneBackgroundColor)) self.Images_Previewer_frame_gridLayout.addWidget(self.__graphicsView) # Signals / Slots. self.__container.engine.imagesCaches.QImage.contentAdded.connect( self.__engine_imagesCaches_QImage__contentAdded) self.Previous_Image_pushButton.clicked.connect( self.__Previous_Image_pushButton__clicked) self.Next_Image_pushButton.clicked.connect( self.__Next_Image_pushButton__clicked) self.Zoom_Out_pushButton.clicked.connect( self.__Zoom_Out_pushButton__clicked) self.Zoom_In_pushButton.clicked.connect( self.__Zoom_In_pushButton__clicked) self.Zoom_Fit_pushButton.clicked.connect( self.__Zoom_Fit_pushButton__clicked) def __Images_Informations_label_setUi(self): """ This method sets the **Images_Informations_label** Widget ui. """ if not self.__displayGraphicsItem: return image = self.__displayGraphicsItem.image self.Images_Informations_label.setText( "{0} - {1}x{2} px - {3} bit".format( os.path.basename(image.data.path), image.data.width, image.data.height, image.data.bpp / 4)) def __engine_imagesCaches_QImage__contentAdded(self, content): """ This method is triggered by the Application **QImage** images cache when content has been added. :param content: Cache added content. ( List ) """ if not self.__paths: return path = foundations.common.getFirstItem(content) if not path in self.__paths: return image = self.__container.engine.imagesCaches.QImage.getContent(path) self.__setDisplayGraphicsItem(image) def __Previous_Image_pushButton__clicked(self, checked): """ This method is triggered when **Previous_Image_pushButton** Widget is clicked. :param checked: Checked state. ( Boolean ) """ self.loopThroughImages(True) def __Next_Image_pushButton__clicked(self, checked): """ This method is triggered when **Next_Image_pushButton** Widget is clicked. :param checked: Checked state. ( Boolean ) """ self.loopThroughImages() def __Zoom_In_pushButton__clicked(self, checked): """ This method is triggered when **Zoom_In_pushButton** Widget is clicked. :param checked: Checked state. ( Boolean ) """ self.scaleView(self.__keyZoomFactor) def __Zoom_Out_pushButton__clicked(self, checked): """ This method is triggered when **Zoom_Out_pushButton** Widget is clicked. :param checked: Checked state. ( Boolean ) """ self.scaleView(1 / self.__keyZoomFactor) def __Zoom_Fit_pushButton__clicked(self, checked): """ This method is triggered when **Zoom_Fit_pushButton** Widget is clicked. :param checked: Checked state. ( Boolean ) """ self.fitImage() def __clearGraphicsScene(self): """ This method clears the View. """ for graphicsItem in self.__graphicsScene.items(): self.__graphicsScene.removeItem(graphicsItem) def __setDisplayGraphicsItem(self, image): """ This method sets the View using given image. :param image: Image to display. ( Qimage ) """ self.__clearGraphicsScene() LOGGER.debug("> Initializing graphics item.") self.__displayGraphicsItem = Image_QGraphicsItem(image=image) self.__graphicsScene.addItem(self.__displayGraphicsItem) self.__Images_Informations_label_setUi() def loadImage(self, index=0): """ This method loads the display image in the View. :param index: Index to load. ( Integer ) :return: Method success. ( Boolean ) """ if not self.__paths: return False image = sibl_gui.ui.common.getImage(self.__paths[index]) self.__setDisplayGraphicsItem(image) return True def scaleView(self, scaleFactor): """ This method scales the Previewer view. :param scaleFactor: Float ( Float ) :return: Method success. ( Boolean ) """ graphicsView = self.findChild(QGraphicsView) factor = graphicsView.matrix().scale(scaleFactor, scaleFactor).mapRect( QRectF(0, 0, 1, 1)).width() if factor < self.__minimumZoomFactor or factor > self.__maximumZoomFactor: return False graphicsView.scale(scaleFactor, scaleFactor) return True def fitWindow(self): """ This method fits the View window. :return: Method success. ( Boolean ) """ if not self.__displayGraphicsItem: return False desktopWidth = QApplication.desktop().screenGeometry( QApplication.desktop().primaryScreen()).width() desktopHeight = QApplication.desktop().screenGeometry( QApplication.desktop().primaryScreen()).height() width = min(desktopWidth * 0.80, self.__displayGraphicsItem.width) height = min(desktopHeight * 0.80, self.__displayGraphicsItem.height) self.resize(width, height) foundations.ui.common.centerWidgetOnScreen(self) return True def fitImage(self): """ This method fits the image to the View. :return: Method success. ( Boolean ) """ if not self.__displayGraphicsItem: return False self.__graphicsView.fitInView( QRectF( -(self.__displayGraphicsItem.width / 2) - (self.__displayGraphicsItemMargin / 2), -(self.__displayGraphicsItem.height / 2) - (self.__displayGraphicsItemMargin / 2), self.__displayGraphicsItem.width + self.__displayGraphicsItemMargin, self.__displayGraphicsItem.height + self.__displayGraphicsItemMargin), Qt.KeepAspectRatio) return True def loopThroughImages(self, backward=False): """ This method loops through View images. :param backward: Looping backward. ( Boolean ) :return: Method success. ( Boolean ) """ index = self.__paths.index(self.__displayGraphicsItem.image.data.path) index += not backward and 1 or -1 if index < 0: index = len(self.__paths) - 1 elif index > len(self.__paths) - 1: index = 0 self.loadImage(index) return True
class WsnViewer(QtGui.QMainWindow, Ui_WsnViewerUI): def __init__(self, parent=None): super(WsnViewer, self).__init__(parent) self.setupUi (self) self.m_nodeNumber = 10 self.m_maxRfRange = 70 self.m_macAddr = MacAddress () self.m_nodeLoc = Coordonate () self.m_nodeList = self.m_macAddr.GetListAddr(10) self.m_edgeList = 0 self.m_edgeDict = {} self.m_nodeLocDict = {} self.m_scene = QGraphicsScene (self.graphicsView) #self.m_paramWin = Ui_paramWindow () self.m_graph = nx.Graph() self.m_data = list () self.DataReader = Data () self.graphicsView.setDragMode (self.graphicsView.RubberBandDrag) self.graphicsView.setRenderHints (QPainter.Antialiasing | QPainter.SmoothPixmapTransform) self.graphicsView.setScene (self.m_scene) self.ConnectActions () def ConnectActions(self): self.actionQuit.triggered.connect (QtGui.qApp.quit) self.actionOpen.triggered.connect (self.OpenWsnTopology) self.actionRandom_topology.triggered.connect (self.GenerateRandWsn) self.actionTools.triggered.connect(self.SetRadioRange) self.actionSave.triggered.connect(self.Save) def OpenWsnTopology (self): fileName = QtGui.QFileDialog.getOpenFileName( self, "Ouvrir un fichier des positions des noeuds", QtCore.QDir.homePath(), "Fichiers texte (*.txt)" ) if (fileName): self.m_data = self.DataReader.ReadFile(fileName) def GenerateRandWsn (self): self.m_nodeList = self.m_macAddr.GetListAddr(self.m_nodeNumber) #print(self.m_nodeList) self.m_edgeList, self.m_nodeLocDict = self.m_nodeLoc.EdgeCreate (self.m_nodeList) #print(self.m_nodeLocDict) #print(self.m_edgeList) self.m_scene.clear() self.DrawGraph () def main (self): self.show() def OpenDialog(self): self.dlg = OpenFileDialog () self.dlg.show() def DrawGraph (self): for edge in self.m_edgeList: self.m_edgeDict [edge] = EdgeItem (self.m_nodeLocDict, edge[0], edge[1]) self.m_scene.addItem(self.m_edgeDict [edge]) for node in self.m_nodeLocDict.iterkeys(): nodeItem = NodeItem (self.m_scene, self.m_nodeLocDict, node) #nodeItem.m_signalLinkNode.nodeItemSignal.connect (self.UpdateNodeLocDict) self.m_scene.addItem (nodeItem) def Save (self): filename = QtGui.QFileDialog.getSaveFileName ( self, "Ouvrir un fichier des positions des noeuds", QtCore.QDir.homePath(), "Fichiers texte (*.txt)" ) if self.m_edgeList and filename: self.DataReader.WriteWSn(self.m_edgeList, self.m_nodeLocDict, filename) def UpdateNodeLocDict (self, nodeName, nodePos): self.m_nodeLocDict [nodeName] = nodePos for node, pos in self.m_nodeLocDict.items(): if node != nodeName: edge = (nodeName, node) if self.Distance(nodePos, pos) <= self.m_maxRfRange: if not edge in self.m_edgeList and not edge[::-1] in self.m_edgeList: self.m_edgeList.append (edge) self.m_edgeDict [edge] = EdgeItem (self.m_nodeLocDict, edge[0], edge[1]) self.m_scene.addItem(self.m_edgeDict [edge]) #print ("%s %s %s" % (node, pos[0], pos[1])) else: self.RemoveLink (edge) self.UpdateNodeList (edge) #print ("%s %s %s" % (node, pos[0], pos[1])) def UpdateNodeList (self, edge): while edge in self.m_edgeList: self.m_edgeList.remove (edge) #while edge[::-1] in self.m_edgeList: # self.m_edgeList.remove (edge[::-1]) def RemoveLink (self, edge): if edge in self.m_edgeDict: self.m_scene.removeItem (self.m_edgeDict [edge]) del self.m_edgeDict [edge] self.m_scene.update() print ("%s %s" % (edge[0], edge[1])) def UpdateScene (self, edge): self.m_scene.addItem(EdgeItem (self.m_nodeLocDict, edge[0], edge[1])) def Distance (self, u, v): x0, y0 = u x1, y1 = v return math.sqrt((x0 - x1)**2 + (y0 - y1)**2) def SetRadioRange (self): self.rfWindow = RfSettingWindow () resultat = self.rfWindow.exec_() if resultat: self.rfWindow.SetRadioValues () self.m_nodeNumber = self.rfWindow.GetNodeNumber () self.m_maxRfRange = self.rfWindow.GetRfRange () self.GenerateRandWsn ()
class SpotguideShowImageDlg(QtGui.QDialog, FORM_CLASS): def __init__(self, theCanvas, theLayer, selFeatureIds, parent=None): super(SpotguideShowImageDlg, self).__init__(parent) self.setupUi(self) self.mTheCanvas = theCanvas self.mTheLayer = theLayer self.mSelFeatureIds = selFeatureIds self.mAllFeatureIds = [] self.highlightList = [] # members shown in graphic view. self.mScene = QGraphicsScene() self.graphicsViewShowImage.setScene(self.mScene) self.mPixmapList = [] featureIter = self.mTheLayer.getFeatures(QgsFeatureRequest().setFlags( QgsFeatureRequest.NoGeometry)) inti = 0 theFeature = QgsFeature() while (featureIter.nextFeature(theFeature) and inti < 512): inti += 1 self.mAllFeatureIds.append(theFeature.id()) self.spinBoxFeatureIndex.setValue(1) self.spinBoxFeatureIndex.setMinimum(1) self.spinBoxFeatureIndex.setMaximum(inti) errMsg = [''] self.initComboBoxOutlinkid() self.selectFeatureById(errMsg, self.mSelFeatureIds[0], bZoomToSelected=False) self.comboBoxOutlinkid.setFocus() self.pushButtonPrev.clicked.connect(self.onPushButtonPrev) self.pushButtonNext.clicked.connect(self.onPushButtonNext) self.connect(self.comboBoxOutlinkid, QtCore.SIGNAL('activated(QString)'), self.comboBoxOutlinkidChanged) def resizeEvent(self, event): self.showImageInGraphicsView() return def disableAllControls(self): self.pushButtonPrev.setEnabled(False) self.pushButtonPrev.setEnabled(False) self.comboBoxOutlinkid.setEnabled(False) self.textEditFeatureInfo.setEnabled(False) return def initComboBoxOutlinkid(self): while (self.comboBoxOutlinkid.count() > 0): self.comboBoxOutlinkid.removeItem(0) for oneFeatureId in self.mSelFeatureIds: featureIter = self.mTheLayer.getFeatures( QgsFeatureRequest(oneFeatureId).setFlags( QgsFeatureRequest.NoGeometry)) theFeature = QgsFeature() if featureIter.nextFeature(theFeature) == False: return if self.mIsMyFeature(theFeature): self.comboBoxOutlinkid.addItem( str(theFeature.attribute('out_link_id'))) def showFeatureDetail(self, errMsg, theFeature): strFeatureInfo = self.getFeatureInfoString(theFeature) self.textEditFeatureInfo.setText(strFeatureInfo) if self.mIsMyFeature(theFeature) == False: return errMsg = [''] pattern_dat, arrow_dat = self.getSpotguidePictures( errMsg, self.mTheLayer, theFeature) if errMsg[0] != '': #QMessageBox.information(self, "Show Spotguide", errMsg[0]) return self.mPixmapList = [] if pattern_dat is not None: patternDatParser = DatParser() patternDatParser.initFromMemory(errMsg, pattern_dat) # pattern picture patternPixmap = QPixmap() patternPixmap.loadFromData( patternDatParser.getDatContentByIndex(errMsg, 0)) self.mPixmapList.append(patternPixmap) if arrow_dat is not None: arrowDatParser = DatParser() arrowDatParser.initFromMemory(errMsg, arrow_dat) # arrow picture arrowPixmap = QPixmap() arrowPixmap.loadFromData( arrowDatParser.getDatContentByIndex(errMsg, 0)) if arrowDatParser.hasPointlist(): # draw the point list on the arrow picture vecCoors = arrowDatParser.getPointListCoordinatesByIndex( errMsg, arrowDatParser.getPointlistIndex()) if errMsg[0] != '': QMessageBox.information(self, "Show Spotguide", errMsg[0]) return with QPainter(arrowPixmap) as thePainter: for oneXYPair in vecCoors: thePainter.setPen(QPen(QColor(255, 0, 0))) thePainter.drawPoint(oneXYPair[0], oneXYPair[1]) thePainter.drawPoint(oneXYPair[0] - 1, oneXYPair[1]) thePainter.drawPoint(oneXYPair[0] + 1, oneXYPair[1]) thePainter.drawPoint(oneXYPair[0], oneXYPair[1] - 1) thePainter.drawPoint(oneXYPair[0], oneXYPair[1] + 1) # append pointlist information to the text box. strPointList = arrowDatParser.getPointListStringByIndex( errMsg, arrowDatParser.getPointlistIndex()) if errMsg[0] != '': QMessageBox.information(self, "Show Spotguide", errMsg[0]) return strTemp = self.textEditFeatureInfo.toPlainText() strTemp += """\n\npointlist:\n""" strTemp += strPointList self.textEditFeatureInfo.setText(strTemp) self.mPixmapList.append(arrowPixmap) self.showImageInGraphicsView() return def showImageInGraphicsView(self): # remove all items in member QGraphicsScene. for oneItem in self.mScene.items(): self.mScene.removeItem(oneItem) for onePixmap in self.mPixmapList: self.mScene.addPixmap( self.getPixMapSizedByWidgt(onePixmap, self.graphicsViewShowImage)) self.mScene.setSceneRect(0, 0, self.graphicsViewShowImage.width() - 5, self.graphicsViewShowImage.height() - 5) return def getFeatureInfoString(self, theFeature): fieldList = theFeature.fields() attrList = theFeature.attributes() strFeatureInfo = "field count: %d\n" % len(fieldList) for oneField, oneAttr in zip(fieldList, attrList): if isinstance(oneAttr, float): strFeatureInfo += "%s: %.0f\n" % (oneField.name(), oneAttr) else: strFeatureInfo += "%s: %s\n" % (oneField.name(), oneAttr) return strFeatureInfo # result[0] is pattern dat # result[1] is arrow dat def getSpotguidePictures(self, errMsg, layer, theFeature): try: uri = QgsDataSourceURI(layer.source()) conn = psycopg2.connect('''host='%s' dbname='%s' user='******' password='******' ''' %\ (uri.host(), uri.database(), uri.username(), uri.password())) pg = conn.cursor() # all these lane's keys must be found # if anyone is not found, a 'KeyError' exception will be thrown. in_link_id = theFeature.attribute('in_link_id') node_id = theFeature.attribute('node_id') out_link_id = theFeature.attribute('out_link_id') passlink_count = theFeature.attribute('passlink_count') pattern_id = theFeature.attribute('pattern_id') arrow_id = theFeature.attribute('arrow_id') # spotguide record filter. strFilter = '''in_link_id=%s and node_id=%s and out_link_id=%s and passlink_count=%s and pattern_id=%s and arrow_id=%s''' % \ (in_link_id, node_id, out_link_id, passlink_count, pattern_id, arrow_id) sqlcmd = \ """SET bytea_output TO escape; select pattern_dat, arrow_dat from %s where %s """ % (uri.table(), strFilter) pg.execute(sqlcmd) row = pg.fetchone() return row[0], row[1] except KeyError, kErr: errMsg[0] = """Selected feature is not a rdb spotguide feature.""" return None, None except Exception, ex: errMsg[0] = ex.message return None, None
class MainForm(QDialog): def __init__(self, parent=None): super(MainForm, self).__init__(parent) self.filename = QString() self.scene = QGraphicsScene(self) self.view = GraphicsView(self.scene) self.view.setScene(self.scene) buttonLayout = QVBoxLayout() for text, slot in ( ("&Open", self.addPixmap), ("&Quit", self.accept)): button = QPushButton(text) self.connect(button, SIGNAL("clicked()"), slot) if text == "&Quit": buttonLayout.addStretch(1) buttonLayout.addWidget(button) buttonLayout.addStretch() layout = QHBoxLayout() layout.addWidget(self.view, 1) layout.addLayout(buttonLayout) self.setLayout(layout) self.setWindowTitle("Petal Picker") def addPixmap(self): path = (QFileInfo(self.filename).path() if not self.filename.isEmpty() else ".") fname = QFileDialog.getOpenFileName(self, "Add Pixmap", path, "Pixmap Files (*.bmp *.jpg *.png *.xpm)") if fname.isEmpty(): return self.createPixmapItem(QPixmap(fname)) def createPixmapItem(self, pixmap, matrix=QMatrix()): global PIX item = QGraphicsPixmapItem(pixmap) PIX = item # item.setFlags(QGraphicsItem.ItemIsSelectable| # QGraphicsItem.ItemIsMovable) item.setMatrix(matrix) self.scene.clearSelection() for i in self.scene.items(): self.scene.removeItem(i) self.scene.addItem(item) self.view.fitInView(item,Qt.KeepAspectRatio) global DIRTY DIRTY = True def accept(self): global DIRTY if not DIRTY: QDialog.accept(self) if DIRTY: if self.confirm(): QDialog.accept(self) else: return def confirm(self): global DIRTY if (DIRTY and QMessageBox.question(self, "Really Quit?", "Really Quit?", QMessageBox.Yes|QMessageBox.No) == QMessageBox.Yes): return True else: return False
class GraphicsWidget(QWidget): def __init__(self, parent=None): QWidget.__init__(self) self._ui = Ui_Form() self._ui.setupUi(self) self.commits = {} self.commits["first"] = ["aaaaa", "bbbbb", "ccccc", "ddddd"] self.commits["second"] = ["ggggg", "bbbbb", "xxxxx", "aaaaa"] self.scene = QGraphicsScene(self) self.view = self._ui.graphicsView self.view.setScene(self.scene) self.view.setRenderHint(QPainter.Antialiasing) self.matching_commits = False self.view.setAcceptDrops(True) self.populate() def populate(self): self.temp_del_items = [] self.commit_items = [] self.filter = my_env_filter() self.scene.installEventFilter(self.filter) self.hints = Hints(0, -60) self.scene.addItem(self.hints) item_x = 0 color = GREEN for branch in self.commits: item_y = 0 for commit_name in self.commits[branch]: commit = Commit(commit_name) commit_background_item = CommitItem(item_x, item_y, color, commit, branch, background_item=True) arrow = Arrow(item_x, item_y, commit_background_item) commit_display_item = CommitItem(item_x, item_y, color, commit, branch, background_item=False) self.scene.addItem(commit_display_item) self.scene.addItem(commit_background_item) self.commit_items.append(commit_background_item) item_y += COMMIT_HEIGHT + ARROW_HEIGHT item_x += 270 color = BLUE self.connect_signals() def clear_scene(self): self.temp_del_items = self.scene.items() for item in self.temp_del_items: self.scene.removeItem(item) # del item def set_matching_commits_mode(self, bool): print "setting matching" self.matching_commits = bool if bool: self.hints.setup_display(step=1) self.hints.update() else: self.commit_item_finished_hovering() def connect_signals(self): self.connect(self.filter, SIGNAL("setMatchingMode(bool)"), self.set_matching_commits_mode) for commit_item in self.commit_items: self.connect(commit_item, SIGNAL("hoveringOverCommitItem(QString*)"), self.commit_item_hovered) self.connect(commit_item, SIGNAL("finishedHovering(void)"), self.commit_item_finished_hovering) self.connect(commit_item, SIGNAL("commitItemInserted(QString*, QString*)"), self.insert_commit) def insert_commit(self, name, branch): self.clear_scene() self.commits[str(branch)].append(name) print self.commits self.populate() def commit_item_hovered(self, commit_name): if self.matching_commits: self.hints.setup_display(step=2) self.hints.update() for commit_item in self.commit_items: if commit_item.get_name() != commit_name: commit_item.gray(True) def commit_item_finished_hovering(self): for commit_item in self.commit_items: commit_item.gray(False)
class Nexus(QMainWindow): """ Die Hauptklasse des Programms. In dieser Klasse wird die GUI gesteuert und die Würfelwürfe aufgerufen. """ dicePoolChanged = pyqtSignal(int) xAgainChanged = pyqtSignal(int) cursed = pyqtSignal(bool) def __init__(self, parent=None): """ Konstruktor """ self.translator_app = QTranslator() self.translator_qt = QTranslator() QApplication.installTranslator( self.translator_app ) QApplication.installTranslator( self.translator_qt ) QWidget.__init__(self, parent) QCoreApplication.setOrganizationName("Caern") QCoreApplication.setOrganizationDomain("www.caern.de") QCoreApplication.setApplicationName("DiceRoller WoD") QCoreApplication.setApplicationVersion(QString.number(PROGRAM_VERSION_MAJOR) + "." + QString.number(PROGRAM_VERSION_MINOR) + "." + QString.number(PROGRAM_VERSION_CHANGE) ) QApplication.setWindowIcon(QIcon(":/icons/logo/WoD.png")) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.createInfo() #self.createLanguageMenu() self.instantRoll = InstantRoll() self.extendedRoll = ExtendedRoll() # Dieser Zähler bestimmt, wie der rollende Würfel angezeigt wird. self.timerDice = QTimer() # Verzögert die tatsächliche Ausführung des Würfelwurfs. self.timerRoll = QTimer() self.populateUi() self.createConnections() self.initializing() self.setWindowTitle(QCoreApplication.applicationName()) #self.retranslateUi() ## Die von der letzten Benutzung gespeicherte Größe und Position auf dem Bildschirm laden. #self.readSettings() #def closeEvent(self, event): #""" #Diese Funktion wird aufgerufen, wann immer das Programm geschlossen wird. #Die Idee dahinter ist, vor dem Beenden, Größe und Position des Fensters zu speichern. #""" #self.writeSettings() #event.accept() def createInfo(self): """ Erzeugt Tooltips und Hilfe für die einzelnen Teile des Programms. """ self.ui.action_houserules.setStatusTip(self.ui.action_houserules.toolTip()) #def createLanguageMenu(self): #""" #Erzeugt das Menü zum Umschalten zwischen den möglichen Sprachen. #""" #self.menu_language = QMenu( self.tr("&Language") ) #self.actionGroup_language = QActionGroup(self) #self.langPath = getPath() + "/" + PROGRAM_LANGUAGE_PATH #self.dir_qm = QDir( self.langPath ); #self.fileNames = self.dir_qm.entryList( QStringList( "DiceRoller-WoD_*.qm" )); ## Englisch hat keine qm-Datei, also muß es von Hand hinzugefügt werden. #self.action = QAction( "&1 English", self.actionGroup_language ) #self.action.setCheckable( True ) #self.action.setData( "en" ) #self.action.setChecked( True ) #self.menu_language.addAction( self.action ) #self.actionGroup_language.addAction( self.action ) #iter = 0 #for i in self.fileNames: #self.trFilename = unicode(i) #self.locale = unicode(i) #self.locale = self.locale[(self.locale.find( "_" )+1):(self.locale.find( "." ))] #self.translator = QTranslator() #self.translator.load( self.trFilename, self.dir_qm.absolutePath() ) #self.language = self.translator.translate( "MainWindow", "English" ) #self.action = QAction( "&" + QString.number(iter + 2) + " " + self.language, self.actionGroup_language ) #self.action.setCheckable( True ) #self.action.setData( self.locale ) #self.menu_language.addAction ( self.action ) #self.actionGroup_language.addAction ( self.action ) #iter += 1 #self.actionGroup_language.triggered.connect(self.switchLanguage) #self.ui.menuBar.insertMenu(self.ui.menuHelp.menuAction(), self.menu_language) #def switchLanguage( self, action ): #""" #Schaltet zwischen den einzelnen Sprachen um. #""" #self.locale = action.data().toString(); #self.qmPath = getPath() + "/" + PROGRAM_LANGUAGE_PATH ##if self.translator_app.load( "DiceRoller-WoD_" + self.locale, self.qmPath ): ##qDebug("Hat DiceRoller-WoD_" + self.locale + " geladen.") ##if self.translator_qt.load( "qt_" + self.locale, QLibraryInfo.location ( QLibraryInfo.TranslationsPath ) ): ##qDebug("Hat qt_" + self.locale + " geladen.") ## Alle Texte neu setzen #self.retranslateUi() ## Seltsamerweise ist retranslate in Ui_MainWindow leer. Ich weiß nicht, wieso das der Fall ist. #self.ui.retranslateUi(self.ui) #def retranslateUi(self): #""" #Diese Funktion übersetzt alle Texte, welche nicht in der .ui-Datei festgelegt sind, sondern im Quellcode (hier) geschrieben wurden. #""" #self.menu_language.setTitle( self.tr( "&Language" ) ) #self.reset() def populateUi(self): self.svgRenderer = QSvgRenderer(":/icons/W10.svg") self.scene = QGraphicsScene() self.view = QGraphicsView() self.view.setFrameShape(QFrame.NoFrame) self.view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.view.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.view.setStyleSheet("background-color: transparent;"); self.view.setScene(self.scene) self.ui.horizontalLayout_dice.insertWidget(1, self.view) def createConnections(self): """ Erstelle die Verbindungen zwischen den verschiedenen Klassen und Elementen des Programms. """ self.ui.action_about.triggered.connect(self.aboutApp) self.ui.action_aboutQt.triggered.connect(QApplication.aboutQt) self.ui.action_houserules.toggled.connect(self.setHouserules) self.ui.pushButton_roll.clicked.connect(self.roll) self.ui.spinBox_pool.valueChanged.connect(self.calcDicePool) self.ui.spinBox_pool.valueChanged.connect(self.reset) self.ui.spinBox_modifier.valueChanged.connect(self.calcDicePoolMod) self.ui.spinBox_modifier.valueChanged.connect(self.reset) self.ui.checkBox_rote.toggled.connect(self.instantRoll.setRote) self.ui.checkBox_rote.toggled.connect(self.extendedRoll.setRote) self.ui.checkBox_rote.toggled.connect(self.reset) self.ui.comboBox_xAgain.activated.connect(self.setXAgainThreshold) self.ui.comboBox_xAgain.activated.connect(self.reset) self.ui.groupBox_extended.toggled.connect(self.changeText) self.ui.radioButton_target.toggled.connect(self.changeText) self.ui.radioButton_maxRolls.toggled.connect(self.changeText) self.ui.groupBox_extended.toggled.connect(self.reset) self.ui.radioButton_target.toggled.connect(self.setExtendedMode) self.ui.spinBox_target.valueChanged.connect(self.extendedRoll.setTarget) self.ui.spinBox_target.valueChanged.connect(self.reset) self.ui.spinBox_maxRolls.valueChanged.connect(self.extendedRoll.setMaxRolls) self.ui.spinBox_maxRolls.valueChanged.connect(self.reset) self.ui.checkBox_rollsLimited.toggled.connect(self.extendedRoll.setLimited) self.ui.checkBox_rollsLimited.toggled.connect(self.reset) self.xAgainChanged.connect(self.instantRoll.setThreshold) self.xAgainChanged.connect(self.extendedRoll.setThreshold) self.cursed.connect(self.instantRoll.setCurse) self.cursed.connect(self.extendedRoll.setCurse) self.instantRoll.rolled.connect(self.setResultSuccesses) self.extendedRoll.rolled.connect(self.setResultSuccesses) self.extendedRoll.rollsNeeded.connect(self.setResultRolls) self.instantRoll.rollFinished.connect(self.setResult) self.extendedRoll.rollFinished.connect(self.setResult) self.dicePoolChanged.connect(self.changeDiceDisplay) self.timerDice.timeout.connect(self.displayDice) self.timerRoll.timeout.connect(self._executeRoll) def initializing(self): """ Initialisiert das Programm mit den Startwerten. """ self.ui.action_quit.setIcon(QIcon(":/icons/actions/exit.png")) self.ui.action_about.setIcon(QIcon(":/icons/logo/WoD.png")) self.ui.pushButton_quit.setIcon(self.ui.action_quit.icon()) self.ui.pushButton_roll.setIcon(QIcon(":icons/W10_0.svg")) self.ui.action_quit.setMenuRole(QAction.QuitRole) self.ui.action_about.setText(self.tr("About %1...").arg(QApplication.applicationName())) self.ui.action_about.setMenuRole(QAction.AboutRole) self.ui.spinBox_pool.setValue(2) self.ui.checkBox_rote.setChecked(False) self.ui.comboBox_xAgain.setCurrentIndex(0) self.ui.spinBox_target.setValue(1) self.changeText() self.ui.radioButton_target.setChecked(True) self.ui.groupBox_extended.setChecked(False) self.ui.checkBox_rollsLimited.setChecked(True) self.dice = [] for i in xrange(10): self.W10_x = QGraphicsSvgItem() self.W10_x.setSharedRenderer(self.svgRenderer) self.W10_x.setElementId("layer" + str(i)) #self.W10_x.setVisible(False) # Ich lege diese Liste an, da ich auf die Liste in self.scene irgendwie nicht zugreifen kann. self.dice.append(self.W10_x) #self.scene.addItem(self.W10_x) def displayDice(self, value=None): """ @todo Der Würfel kann mehrmals in Folge das selbe Ergebnis anzeigen, was dazu führt, daß der Bildablauf zu stocken scheint. """ if (value == None): dieValue = Random.random(10)-1 else: dieValue = value for item in self.scene.items(): self.scene.removeItem(item) self.scene.addItem(self.dice[dieValue]) self.view.setSceneRect(self.scene.itemsBoundingRect()) self.view.fitInView(self.dice[dieValue]) def changeDiceDisplay(self, number): """ Diese Funktion bestimmt, wieviele Würfel angezeigt werden. """ pass #if (self.ui.horizontalLayout_dice.count > 2): #pass #randomValue = Random.random(10)-1 #for die in xrange(number): #self.__W10_scene = QGraphicsScene() #self.__W10_scene.addItem(self.dice[randomValue]) #self.__W10_view = QGraphicsView() #self.__W10_view.setScene(self.__W10_scene) #self.__W10_view.setSceneRect(self.scene.itemsBoundingRect()) #self.__W10_view.fitInView(self.dice[randomValue]) #self.ui.horizontalLayout_dice.insertWidget(1, self.__W10_view) def aboutApp(self): """ Zeigt die Info-Nachricht an. """ self.appText = self.tr(""" <h1>%1</h1> <h2>Version: %2</h2> <p>Copyright (C) 2011 by Victor von Rhein<br> EMail: [email protected]</p> """).arg(QCoreApplication.applicationName()).arg(QCoreApplication.applicationVersion()) self.gnuText = self.tr(""" <h2>GNU General Public License</h2> <p>This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</p> <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.</p> <p>You should have received a copy of the GNU General Public License along with this program. If not, see <a>http://www.gnu.org/licenses/</a>.</p> """) self.wodText = self.tr(""" <h2>%1</h2> <p>%1, %2, the %3 and all referring terms and symbols are copyrighted by %4</p> """).arg("World of Darkness").arg("White Wolf").arg("White Wolf-Logo").arg("White Wolf Inc.") self.aboutText = self.appText + self.gnuText + self.wodText QMessageBox.about(self, "About " + QCoreApplication.applicationName(), self.aboutText ) def roll(self): """ Der Wurf wird durchgeführt. Der tatsächliche Wurf wird aber von den Timern angestoßen. """ # Es wird ein rollender Würfel angezeigt. self.timerDice.start(DICEROLL_TIMER_INTERVAL) self.timerRoll.start(DICEROLL_TIMER_DELAY) def _executeRoll(self): """ Entscheidet vor dem eigentlichen Würfelwurf, ob ein normaler oder ein erweiterter Wurf notwendig ist und führt diesen aus. """ if self.ui.groupBox_extended.isChecked(): #qDebug("Checked") self.extendedRoll.roll() else: #qDebug("Not Checked") self.instantRoll.roll() # Die Anzeige des rollenden Würfels wird angehalten self.timerDice.stop() self.timerRoll.stop() def calcDicePool(self, value): """ Berechnet die Größe des zur Verfügung stehenden Würfelpools, welcher von den Würfeln und den Modifikatoren abhängt. """ self.instantRoll.poolSize = value + self.ui.spinBox_modifier.value() self.extendedRoll.poolSize = self.instantRoll.poolSize self.extendedRoll.limit = value self.dicePoolChanged.emit(self.instantRoll.poolSize) def calcDicePoolMod(self, value): """ Berechnet wie schon calcDicePool() die Größe des Würfelvorrats, allerdings werden dieser Funktion andere Argumente übergeben. """ self.instantRoll.poolSize = value + self.ui.spinBox_pool.value() self.extendedRoll.poolSize = value + self.ui.spinBox_pool.value() def setHouserules(self, value): #qDebug("Test" + str(value)) self.extendedRoll.isHouserules = value def setXAgainThreshold(self, value): """ Legt fest, bei welchem Ergebnis weitergewürfelt werden kann und wann dies überhaupt nicht der Fall sein sollte oder gar Erfolge abgezogen werden können. """ self.__threshold = 0 if (value < 3): self.__threshold = 10 - value # Index 0 entspricht 10 again, 1 entspricht 9 again etc. else: self.__threshold = 11 # Index 3 entspricht "no reroll" self.xAgainChanged.emit(self.__threshold) if (value > 3): self.cursed.emit(True) # Kein reroll und 1er werden Abgezogen. else: self.cursed.emit(False) # 1er werden nicht abgezogen. def setExtendedMode(self, sw): """ Legt den Modus fest, mit welchem der erweiterte Wurf durchgeführt wird. Entweder wird auf ein Ergebnishingewürfelt, oder nach einer bestimmten Anzahl würde die Anzahl der Erfolge gezählt. """ if (sw): self.extendedRoll.isResultInRolls = False else: self.extendedRoll.isResultInRolls = True def setResult(self, value): """ Schreibt das Ergebnis des Wurfs in die GUI. Dabei wird auch je nach Erfolgsqualität bei dem dargestellten Würfel eine andere Augenzahl gezeigt. """ self.ui.statusBar.showMessage(self.tr("Result of diceroll is displayed.")) if (value == DieResult.dramaticFailure): self.ui.label_resultText.setText(self.tr("Dramatic Failure")); self.ui.label_result.setPixmap(QPixmap(":/icons/actions/cnrdelete-all1.png")); self.displayDice(1) elif (value == DieResult.failure): self.ui.label_resultText.setText(self.tr("Failure")); self.ui.label_result.setPixmap(QPixmap(":/icons/actions/fileclose.png")); self.displayDice(Random.random(2, 7)) elif (value == DieResult.success): self.ui.label_resultText.setText(self.tr("Success")); self.ui.label_result.setPixmap(QPixmap(":/icons/actions/ok.png")); self.displayDice(Random.random(8, 9)) else: self.ui.label_resultText.setText(self.tr("Exceptional Success")); self.ui.label_result.setPixmap(QPixmap(":/icons/actions/bookmark.png")); self.displayDice(0) def setResultRolls(self, value): """ Zeigt in der GUI an, wieviele Würfe nötig waren. """ if (self.ui.groupBox_extended.isChecked() and self.ui.radioButton_target.isChecked()): self.ui.lcdNumber_successes.display(value) def setResultSuccesses(self, value): """ Zeigt in der GUI an, wieviele Erfolge erzielt wurden. """ if (not self.ui.groupBox_extended.isChecked() or not self.ui.radioButton_target.isChecked()): self.ui.lcdNumber_successes.display(value) def changeText(self): """ Verändert den Text in der Statuszeile. """ if (self.ui.groupBox_extended.isChecked() and self.ui.radioButton_target.isChecked()): self.ui.label_successes.setText(self.tr("Number of rolls needed:")) else: self.ui.label_successes.setText(self.tr("Number of successes:")) def reset(self): """ Setzt das Programm auf einen definierten Startwert zurück. """ self.ui.label_result.setPixmap(QPixmap(":/icons/actions/fileclose.png")) self.ui.label_resultText.setText(self.tr("No result yet!")) self.ui.lcdNumber_successes.display(0) self.ui.statusBar.showMessage(self.tr("Press the Button to roll the dice!"))
class DualImageView(QGraphicsView): VERTICAL = 0 HORIZONTAL = 1 IMAGE_A = 0 IMAGE_B = 1 # no argument signal images_changed = pyqtSignal() annotations_changed = pyqtSignal() annotation_selected = pyqtSignal(int) no_selection = pyqtSignal() # click/point signals image_a_click = pyqtSignal(int,int) image_b_click = pyqtSignal(int,int) # keyboard key_event = pyqtSignal(int) def __init__(self, main_win): super(QGraphicsView,self).__init__(main_win) self.parent_ = main_win self.main_win_ = main_win self.setInteractive(True) self.setStyleSheet("QGraphicsView { border: none; }") self.scene_ = QGraphicsScene(0,0,0,0,self.parent_) self.image_item_ = self.scene_.addPixmap(QPixmap()) self.image_item_.setPos(0,0) #self.ann_group_ = QGraphicsItemGroup() #self.ann_group_.setPos(0,0) #self.scene_.addItem(self.ann_group_) self.setScene(self.scene_) self.scene_.selectionChanged.connect(self.on_selection_changed) # TODO: handle orientation self.orientation_ = DualImageView.VERTICAL self.images_ = [None, None] self.composite_ = None self.annotations_ = [] self.dim_ = 0 self.offset_ = np.array([0,0]) self.cancel_click_ = False self.images_changed.connect(self.on_images_changed) self.annotations_changed.connect(self.on_annotations_changed) def on_selection_changed(self): log.debug("on_selection_changed") selected = self.scene_.selectedItems() if len(selected) > 0: self.cancel_click_ = True selected = self.scene_.selectedItems()[0] idx = -1 for a in self.annotations_: idx += 1 if a.item == selected: log.debug(" emitting selection {0}".format(idx)) self.annotation_selected.emit(idx) else: self.no_selection.emit() @property def image_b_offset(self): return np.array([0,self.dim_],dtype=np.int32) def point_in_image(self, p): if p[1] < self.dim_: return 0 else: return 1 def point_to_image(self, which, p): if which == DualImageView.IMAGE_B: return p - self.image_b_offset return p def image_to_view(self, which, p): if which == DualImageView.IMAGE_B: return p + self.image_b_offset return p def on_images_changed(self): imga = self.images_[0] imgb = self.images_[1] width = max(imga.shape[1],imgb.shape[1]) heighta = imga.shape[0] heightb = imgb.shape[0] height = heighta + heightb self.dim_ = heighta self.offset_ = np.array([0,heighta]) # this assumes rgb images :-( comp = np.empty((height,width,imga.shape[2]),dtype=imga.dtype) comp[0:heighta,:imga.shape[1],:] = imga comp[heighta:(heighta+heightb),:imgb.shape[1],:] = imgb self.composite_ = comp qimg = qn.array2qimage(self.composite_) pix = QPixmap.fromImage(qimg) self.image_item_.setPixmap(pix) self.scene_.setSceneRect(0,0, width, height) self.repaint() def on_annotations_changed(self): #log.debug("on_annotations_changed") # self.scene_.removeItem(self.ann_group_) # self.ann_group_ = QGraphicsItemGroup() # self.ann_group_.setHandlesChildEvents(False) # self.ann_group_.setPos(0,0) # for a in self.annotations_: # log.debug(" adding item") # self.ann_group_.addToGroup(a.get_item()) # self.scene_.addItem(self.ann_group_) self.repaint() def transform_raw_pt(self, ev): pt = self.mapToScene(ev.x(), ev.y()) return np.array([int(pt.x()), int(pt.y())], dtype=np.int32) def clear(self): self.images_ = [None,None] self.composite_ = None self.annotations_ = None self.images_changed.emit() self.annotations_changed.emit() def set_images(self, img_pair): self.images_ = img_pair self.images_changed.emit() # @property # def annotations(self): # return self.annotations_ # @annotations.setter # def annotations(self, anns): # self.annotations_ = anns # self.annotations_changed.emit() # def set_annotations(self, anns): # self.annotations_ = anns # self.annotations_changed.emit() def paintEvent(self, ev): painter = QPainter(self.viewport()) painter.fillRect(0,0,self.viewport().width(),self.viewport().height(), QColor(0,0,0)) painter.end() QGraphicsView.paintEvent(self, ev) def annotation(self, idx): return self.annotations_[idx] def clear_annotations(self): for a in self.annotations_: self.scene_.removeItem(a.item) self.annotations_ = [] self.annotations_changed.emit() def add_annotation(self, ann): ann.changed.connect(self.on_annotations_changed) self.annotations_.append(ann) self.scene_.addItem(ann.item) self.annotations_changed.emit() return len(self.annotations_) - 1 def remove_last_annotation(self): self.scene_.removeItem(self.annotations_[-1].item) del self.annotations_[-1] self.annotations_changed.emit() def remove_annotation(self, idx): self.scene_.removeItem(self.annotations_[idx].item) del self.annotations_[idx] self.annotations_changed.emit() def mousePressEvent(self, ev): super(DualImageView,self).mousePressEvent(ev) if self.cancel_click_: return log.debug("mouse pressed: " + str(ev)) self.img_local_pt = self.transform_raw_pt(ev) def mouseReleaseEvent(self, ev): super(DualImageView,self).mouseReleaseEvent(ev) if self.cancel_click_: self.cancel_click_ = False return log.debug("mouse released: " + str(ev)) rel_pt = self.transform_raw_pt(ev) delta = rel_pt - self.img_local_pt if abs(delta[0]) < 3 and abs(delta[1] < 3): # it was a successful click self.mouseClicked(self.img_local_pt) else: # recognize this as a rectangle drag self.mouseDragged(self.img_local_pt, delta) def mouseDragged(self, pt, delta): log.debug("mouse dragged: {0}, {1}".format(pt,delta)) def mouseClicked(self, pt): log.debug("mouse clicked: {0}".format(pt)) if pt[1] < self.dim_: self.image_a_click.emit(pt[0],pt[1]) else: self.image_b_click.emit(pt[0],pt[1] - self.dim_) # handle the keyboard events here! def keyPressEvent(self, ev): pass def keyReleaseEvent(self, ev): k = ev.key() self.key_event.emit(k)
class ImagesPreviewer(foundations.ui.common.QWidgetFactory(uiFile=UI_FILE)): """ | This class provides the Application images previewer. | It defines methods to navigate through the list of given images ( List of images paths ), zoom in / out and fit the displayed image, etc... """ def __init__(self, parent, paths=None, *args, **kwargs): """ This method initializes the class. :param parent: Object parent. ( QObject ) :param paths: Images paths. ( Tuple / List ) :param \*args: Arguments. ( \* ) :param \*\*kwargs: Keywords arguments. ( \*\* ) """ LOGGER.debug("> Initializing '{0}()' class.".format(self.__class__.__name__)) super(ImagesPreviewer, self).__init__(parent, *args, **kwargs) # --- Setting class attributes. --- self.__container = parent self.__paths = None self.paths = paths self.__uiResourcesDirectory = "resources" self.__uiResourcesDirectory = os.path.join(os.path.dirname(__file__), self.__uiResourcesDirectory) self.__uiPreviousImage = "Previous.png" self.__uiNextImage = "Next.png" self.__uiZoomOutImage = "Zoom_Out.png" self.__uiZoomInImage = "Zoom_In.png" # Ensure the ui object is destroyed on close to avoid memory leaks. self.setAttribute(Qt.WA_DeleteOnClose) self.__graphicsSceneBackgroundColor = QColor(32, 32, 32) self.__minimumZoomFactor = 0.05 self.__maximumZoomFactor = 25 self.__displayGraphicsItemMargin = 32 self.__graphicsSceneWidth = QApplication.desktop().screenGeometry( QApplication.desktop().primaryScreen()).width() * (1 / self.__minimumZoomFactor * 1.75) self.__graphicsSceneHeight = QApplication.desktop().screenGeometry( QApplication.desktop().primaryScreen()).height() * (1 / self.__minimumZoomFactor * 1.75) self.__wheelZoomFactor = 350.0 self.__keyZoomFactor = 1.20 self.__graphicsView = None self.__graphicsScene = None self.__displayGraphicsItem = None ImagesPreviewer.__initializeUi(self) self.loadImage() #****************************************************************************************************************** #*** Attributes properties. #****************************************************************************************************************** @property def container(self): """ This method is the property for **self.__container** attribute. :return: self.__container. ( QObject ) """ return self.__container @container.setter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def container(self, value): """ This method is the setter method for **self.__container** attribute. :param value: Attribute value. ( QObject ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "container")) @container.deleter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def container(self): """ This method is the deleter method for **self.__container** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "container")) @property def paths(self): """ This method is the property for **self.__paths** attribute. :return: self.__paths. ( Tuple / List ) """ return self.__paths @paths.setter @foundations.exceptions.handleExceptions(AssertionError) def paths(self, value): """ This method is the setter method for **self.__paths** attribute. :param value: Attribute value. ( Tuple / List ) """ if value is not None: assert type(value) in (tuple, list), "'{0}' attribute: '{1}' type is not 'tuple' or 'list'!".format("paths", value) for element in value: assert type(element) is unicode, "'{0}' attribute: '{1}' type is not 'unicode'!".format( "paths", element) self.__paths = value @paths.deleter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def paths(self): """ This method is the deleter method for **self.__paths** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "paths")) @property def uiResourcesDirectory(self): """ This method is the property for **self.__uiResourcesDirectory** attribute. :return: self.__uiResourcesDirectory. ( String ) """ return self.__uiResourcesDirectory @uiResourcesDirectory.setter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def uiResourcesDirectory(self, value): """ This method is the setter method for **self.__uiResourcesDirectory** attribute. :param value: Attribute value. ( String ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "uiResourcesDirectory")) @uiResourcesDirectory.deleter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def uiResourcesDirectory(self): """ This method is the deleter method for **self.__uiResourcesDirectory** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "uiResourcesDirectory")) @property def uiPreviousImage(self): """ This method is the property for **self.__uiPreviousImage** attribute. :return: self.__uiPreviousImage. ( String ) """ return self.__uiPreviousImage @uiPreviousImage.setter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def uiPreviousImage(self, value): """ This method is the setter method for **self.__uiPreviousImage** attribute. :param value: Attribute value. ( String ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "uiPreviousImage")) @uiPreviousImage.deleter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def uiPreviousImage(self): """ This method is the deleter method for **self.__uiPreviousImage** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "uiPreviousImage")) @property def uiNextImage(self): """ This method is the property for **self.__uiNextImage** attribute. :return: self.__uiNextImage. ( String ) """ return self.__uiNextImage @uiNextImage.setter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def uiNextImage(self, value): """ This method is the setter method for **self.__uiNextImage** attribute. :param value: Attribute value. ( String ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "uiNextImage")) @uiNextImage.deleter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def uiNextImage(self): """ This method is the deleter method for **self.__uiNextImage** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "uiNextImage")) @property def uiZoomOutImage(self): """ This method is the property for **self.__uiZoomOutImage** attribute. :return: self.__uiZoomOutImage. ( String ) """ return self.__uiZoomOutImage @uiZoomOutImage.setter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def uiZoomOutImage(self, value): """ This method is the setter method for **self.__uiZoomOutImage** attribute. :param value: Attribute value. ( String ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "uiZoomOutImage")) @uiZoomOutImage.deleter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def uiZoomOutImage(self): """ This method is the deleter method for **self.__uiZoomOutImage** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "uiZoomOutImage")) @property def uiZoomInImage(self): """ This method is the property for **self.__uiZoomInImage** attribute. :return: self.__uiZoomInImage. ( String ) """ return self.__uiZoomInImage @uiZoomInImage.setter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def uiZoomInImage(self, value): """ This method is the setter method for **self.__uiZoomInImage** attribute. :param value: Attribute value. ( String ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "uiZoomInImage")) @uiZoomInImage.deleter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def uiZoomInImage(self): """ This method is the deleter method for **self.__uiZoomInImage** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "uiZoomInImage")) @property def graphicsSceneBackgroundColor(self): """ This method is the property for **self.__graphicsSceneBackgroundColor** attribute. :return: self.__graphicsSceneBackgroundColor. ( QColors ) """ return self.__graphicsSceneBackgroundColor @graphicsSceneBackgroundColor.setter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def graphicsSceneBackgroundColor(self, value): """ This method is the setter method for **self.__graphicsSceneBackgroundColor** attribute. :param value: Attribute value. ( QColors ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "graphicsSceneBackgroundColor")) @graphicsSceneBackgroundColor.deleter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def graphicsSceneBackgroundColor(self): """ This method is the deleter method for **self.__graphicsSceneBackgroundColor** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "graphicsSceneBackgroundColor")) @property def graphicsSceneWidth(self): """ This method is the property for **self.__graphicsSceneWidth** attribute. :return: self.__graphicsSceneWidth. ( Integer ) """ return self.__graphicsSceneWidth @graphicsSceneWidth.setter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def graphicsSceneWidth(self, value): """ This method is the setter method for **self.__graphicsSceneWidth** attribute. :param value: Attribute value. ( Integer ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "graphicsSceneWidth")) @graphicsSceneWidth.deleter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def graphicsSceneWidth(self): """ This method is the deleter method for **self.__graphicsSceneWidth** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "graphicsSceneWidth")) @property def graphicsSceneHeight(self): """ This method is the property for **self.__graphicsSceneHeight** attribute. :return: self.__graphicsSceneHeight. ( Object ) """ return self.__graphicsSceneHeight @graphicsSceneHeight.setter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def graphicsSceneHeight(self, value): """ This method is the setter method for **self.__graphicsSceneHeight** attribute. :param value: Attribute value. ( Object ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "graphicsSceneHeight")) @graphicsSceneHeight.deleter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def graphicsSceneHeight(self): """ This method is the deleter method for **self.__graphicsSceneHeight** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "graphicsSceneHeight")) @property def minimumZoomFactor(self): """ This method is the property for **self.__minimumZoomFactor** attribute. :return: self.__minimumZoomFactor. ( Float ) """ return self.__minimumZoomFactor @minimumZoomFactor.setter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def minimumZoomFactor(self, value): """ This method is the setter method for **self.__minimumZoomFactor** attribute. :param value: Attribute value. ( Float ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "minimumZoomFactor")) @minimumZoomFactor.deleter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def minimumZoomFactor(self): """ This method is the deleter method for **self.__minimumZoomFactor** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "minimumZoomFactor")) @property def maximumZoomFactor(self): """ This method is the property for **self.__maximumZoomFactor** attribute. :return: self.__maximumZoomFactor. ( Float ) """ return self.__maximumZoomFactor @maximumZoomFactor.setter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def maximumZoomFactor(self, value): """ This method is the setter method for **self.__maximumZoomFactor** attribute. :param value: Attribute value. ( Float ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "maximumZoomFactor")) @maximumZoomFactor.deleter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def maximumZoomFactor(self): """ This method is the deleter method for **self.__maximumZoomFactor** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "maximumZoomFactor")) @property def wheelZoomFactor(self): """ This method is the property for **self.__wheelZoomFactor** attribute. :return: self.__wheelZoomFactor. ( Float ) """ return self.__wheelZoomFactor @wheelZoomFactor.setter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def wheelZoomFactor(self, value): """ This method is the setter method for **self.__wheelZoomFactor** attribute. :param value: Attribute value. ( Float ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "wheelZoomFactor")) @wheelZoomFactor.deleter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def wheelZoomFactor(self): """ This method is the deleter method for **self.__wheelZoomFactor** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "wheelZoomFactor")) @property def keyZoomFactor(self): """ This method is the property for **self.__keyZoomFactor** attribute. :return: self.__keyZoomFactor. ( Float ) """ return self.__keyZoomFactor @keyZoomFactor.setter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def keyZoomFactor(self, value): """ This method is the setter method for **self.__keyZoomFactor** attribute. :param value: Attribute value. ( Float ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "keyZoomFactor")) @keyZoomFactor.deleter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def keyZoomFactor(self): """ This method is the deleter method for **self.__keyZoomFactor** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "keyZoomFactor")) @property def graphicsView(self): """ This method is the property for **self.__graphicsView** attribute. :return: self.__graphicsView. ( QGraphicsView ) """ return self.__graphicsView @graphicsView.setter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def graphicsView(self, value): """ This method is the setter method for **self.__graphicsView** attribute. :param value: Attribute value. ( QGraphicsView ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "graphicsView")) @graphicsView.deleter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def graphicsView(self): """ This method is the deleter method for **self.__graphicsView** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "graphicsView")) @property def graphicsScene(self): """ This method is the property for **self.__graphicsScene** attribute. :return: self.__graphicsScene. ( QGraphicsScene ) """ return self.__graphicsScene @graphicsScene.setter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def graphicsScene(self, value): """ This method is the setter method for **self.__graphicsScene** attribute. :param value: Attribute value. ( QGraphicsScene ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "graphicsScene")) @graphicsScene.deleter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def graphicsScene(self): """ This method is the deleter method for **self.__graphicsScene** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "graphicsScene")) @property def displayGraphicsItem(self): """ This method is the property for **self.__displayGraphicsItem** attribute. :return: self.__displayGraphicsItem. ( QGraphicsItem ) """ return self.__displayGraphicsItem @displayGraphicsItem.setter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def displayGraphicsItem(self, value): """ This method is the setter method for **self.__displayGraphicsItem** attribute. :param value: Attribute value. ( QGraphicsItem ) """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "displayGraphicsItem")) @displayGraphicsItem.deleter @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError) def displayGraphicsItem(self): """ This method is the deleter method for **self.__displayGraphicsItem** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "displayGraphicsItem")) #****************************************************************************************************************** #*** Class methods. #****************************************************************************************************************** def show(self): """ This method reimplements the :meth:`QWidget.show` method. """ super(ImagesPreviewer, self).show() foundations.ui.common.centerWidgetOnScreen(self) def closeEvent(self, event): """ This method reimplements the :meth:`QWidget.closeEvent` method. :param event: QEvent ( QEvent ) """ LOGGER.debug("> Removing '{0}' from Images Previewers list.".format(self)) self.__container.imagesPreviewers.remove(self) event.accept() def wheelEvent(self, event): """ This method reimplements the :meth:`QWidget.wheelEvent` method. :param event: QEvent ( QEvent ) """ self.scaleView(pow(1.5, event.delta() / self.__wheelZoomFactor)) def keyPressEvent(self, event): """ This method reimplements the :meth:`QWidget.keyPressEvent` method. :param event: QEvent ( QEvent ) """ key = event.key() if key == Qt.Key_Plus: self.scaleView(self.__keyZoomFactor) elif key == Qt.Key_Minus: self.scaleView(1 / self.__keyZoomFactor) else: super(ImagesPreviewer, self).keyPressEvent(event) def __initializeUi(self): """ This method initializes the Widget ui. """ LOGGER.debug("> Initializing '{0}' ui.".format(self.__class__.__name__)) self.Previous_Image_pushButton.setIcon(QIcon(os.path.join(self.__uiResourcesDirectory, self.__uiPreviousImage))) self.Next_Image_pushButton.setIcon(QIcon(os.path.join(self.__uiResourcesDirectory, self.__uiNextImage))) self.Zoom_In_pushButton.setIcon(QIcon(os.path.join(self.__uiResourcesDirectory, self.__uiZoomInImage))) self.Zoom_Out_pushButton.setIcon(QIcon(os.path.join(self.__uiResourcesDirectory, self.__uiZoomOutImage))) len(self.__paths) <= 1 and self.Navigation_frame.hide() LOGGER.debug("> Initializing graphics View.") self.__graphicsView = QGraphicsView() self.__graphicsView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.__graphicsView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.__graphicsView.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) self.__graphicsView.setDragMode(QGraphicsView.ScrollHandDrag) # Reimplementing QGraphicsView wheelEvent method. self.__graphicsView.wheelEvent = self.wheelEvent LOGGER.debug("> Initializing graphics scene.") self.__graphicsScene = QGraphicsScene(self.__graphicsView) self.__graphicsScene.setItemIndexMethod(QGraphicsScene.NoIndex) self.__graphicsScene.setSceneRect(-(float(self.__graphicsSceneWidth)) / 2, - (float(self.__graphicsSceneHeight)) / 2, float(self.__graphicsSceneWidth), float(self.__graphicsSceneHeight)) self.__graphicsView.setScene(self.__graphicsScene) self.__graphicsView.setBackgroundBrush(QBrush(self.__graphicsSceneBackgroundColor)) self.Images_Previewer_frame_gridLayout.addWidget(self.__graphicsView) # Signals / Slots. self.__container.engine.imagesCaches.QImage.contentAdded.connect(self.__engine_imagesCaches_QImage__contentAdded) self.Previous_Image_pushButton.clicked.connect(self.__Previous_Image_pushButton__clicked) self.Next_Image_pushButton.clicked.connect(self.__Next_Image_pushButton__clicked) self.Zoom_Out_pushButton.clicked.connect(self.__Zoom_Out_pushButton__clicked) self.Zoom_In_pushButton.clicked.connect(self.__Zoom_In_pushButton__clicked) self.Zoom_Fit_pushButton.clicked.connect(self.__Zoom_Fit_pushButton__clicked) def __Images_Informations_label_setUi(self): """ This method sets the **Images_Informations_label** Widget ui. """ if not self.__displayGraphicsItem: return image = self.__displayGraphicsItem.image self.Images_Informations_label.setText("{0} - {1}x{2} px - {3} bit".format(os.path.basename(image.data.path), image.data.width, image.data.height, image.data.bpp / 4)) def __engine_imagesCaches_QImage__contentAdded(self, content): """ This method is triggered by the Application **QImage** images cache when content has been added. :param content: Cache added content. ( List ) """ if not self.__paths: return path = foundations.common.getFirstItem(content) if not path in self.__paths: return image = self.__container.engine.imagesCaches.QImage.getContent(path) self.__setDisplayGraphicsItem(image) def __Previous_Image_pushButton__clicked(self, checked): """ This method is triggered when **Previous_Image_pushButton** Widget is clicked. :param checked: Checked state. ( Boolean ) """ self.loopThroughImages(True) def __Next_Image_pushButton__clicked(self, checked): """ This method is triggered when **Next_Image_pushButton** Widget is clicked. :param checked: Checked state. ( Boolean ) """ self.loopThroughImages() def __Zoom_In_pushButton__clicked(self, checked): """ This method is triggered when **Zoom_In_pushButton** Widget is clicked. :param checked: Checked state. ( Boolean ) """ self.scaleView(self.__keyZoomFactor) def __Zoom_Out_pushButton__clicked(self, checked): """ This method is triggered when **Zoom_Out_pushButton** Widget is clicked. :param checked: Checked state. ( Boolean ) """ self.scaleView(1 / self.__keyZoomFactor) def __Zoom_Fit_pushButton__clicked(self, checked): """ This method is triggered when **Zoom_Fit_pushButton** Widget is clicked. :param checked: Checked state. ( Boolean ) """ self.fitImage() def __clearGraphicsScene(self): """ This method clears the View. """ for graphicsItem in self.__graphicsScene.items(): self.__graphicsScene.removeItem(graphicsItem) def __setDisplayGraphicsItem(self, image): """ This method sets the View using given image. :param image: Image to display. ( Qimage ) """ self.__clearGraphicsScene() LOGGER.debug("> Initializing graphics item.") self.__displayGraphicsItem = Image_QGraphicsItem(image=image) self.__graphicsScene.addItem(self.__displayGraphicsItem) self.__Images_Informations_label_setUi() def loadImage(self, index=0): """ This method loads the display image in the View. :param index: Index to load. ( Integer ) :return: Method success. ( Boolean ) """ if not self.__paths: return False image = sibl_gui.ui.common.getImage(self.__paths[index]) self.__setDisplayGraphicsItem(image) return True def scaleView(self, scaleFactor): """ This method scales the Previewer view. :param scaleFactor: Float ( Float ) :return: Method success. ( Boolean ) """ graphicsView = self.findChild(QGraphicsView) factor = graphicsView.matrix().scale(scaleFactor, scaleFactor).mapRect(QRectF(0, 0, 1, 1)).width() if factor < self.__minimumZoomFactor or factor > self.__maximumZoomFactor: return False graphicsView.scale(scaleFactor, scaleFactor) return True def fitWindow(self): """ This method fits the View window. :return: Method success. ( Boolean ) """ if not self.__displayGraphicsItem: return False desktopWidth = QApplication.desktop().screenGeometry(QApplication.desktop().primaryScreen()).width() desktopHeight = QApplication.desktop().screenGeometry(QApplication.desktop().primaryScreen()).height() width = min(desktopWidth * 0.80, self.__displayGraphicsItem.width) height = min(desktopHeight * 0.80, self.__displayGraphicsItem.height) self.resize(width, height) foundations.ui.common.centerWidgetOnScreen(self) return True def fitImage(self): """ This method fits the image to the View. :return: Method success. ( Boolean ) """ if not self.__displayGraphicsItem: return False self.__graphicsView.fitInView( QRectF(-(self.__displayGraphicsItem.width / 2) - (self.__displayGraphicsItemMargin / 2), - (self.__displayGraphicsItem.height / 2) - (self.__displayGraphicsItemMargin / 2), self.__displayGraphicsItem.width + self.__displayGraphicsItemMargin, self.__displayGraphicsItem.height + self.__displayGraphicsItemMargin), Qt.KeepAspectRatio) return True def loopThroughImages(self, backward=False): """ This method loops through View images. :param backward: Looping backward. ( Boolean ) :return: Method success. ( Boolean ) """ index = self.__paths.index(self.__displayGraphicsItem.image.data.path) index += not backward and 1 or -1 if index < 0: index = len(self.__paths) - 1 elif index > len(self.__paths) - 1: index = 0 self.loadImage(index) return True
class PlotPreview(QDialog): def __init__(self, thread, parent): QDialog.__init__(self, parent) self.ui = Ui_PlotPreview() self.ui.setupUi(self) self.parent = parent icon = QIcon() icon.addPixmap(QPixmap(":/icons/reload.png"), QIcon.Normal, QIcon.Off) self.update_btn = QPushButton(icon, "Update") self.ui.buttonBox.addButton(self.update_btn, QDialogButtonBox.ApplyRole) self.update_btn.clicked.connect(self.render_image) self._pix = None self._image_list = None self._thread = thread self.scene = QGraphicsScene() self.scene.setSceneRect(0,0,1,1) self.ui.imageView.setScene(self.scene) self.pix_item = None self.ui.imageView.setEnabled(False) self.ui.imageView.setInteractive(False) self.ui.imageView.setDragMode(QGraphicsView.ScrollHandDrag) self.pic_w = None self.pic_c = None self.show_pic_w = None self.show_pic_c = None timer = QTimer(self) timer.setSingleShot(True) timer.setInterval(500) self.timer = timer timer.timeout.connect(self.render_image) if parameters.instance.use_OpenGL: self.ui.imageView.setViewport(QGLWidget(QGLFormat(QGL.SampleBuffers))) def __del__(self): self.ui.imageView.setScene(None) cleanQObject(self) @property def thread(self): '''Thread object drawing the img''' return self._thread @thread.setter def thread(self, value): if self._thread != value: self._thread = value @property def pix(self): '''Image to be previewed. :returntype: `QImage`''' return self._pix @pix.setter def pix(self, pix): self.ui.imageView.setEnabled(True) self._pix = pix if self.pix_item is not None: self.scene.removeItem(self.pix_item) self.pix_item = self.scene.addPixmap(QPixmap.fromImage(pix)) self.scene.setSceneRect(QRectF(self.pix.rect())) if self.show_pic_w: self.show_pic_w.close() self.show_pic_w = None if self.pic_w: self.show_pic_w = QLabel(self, Qt.Window) #self.show_pic_w.setAttribute(Qt.WA_DeleteOnClose) self.show_pic_w.setPicture(self.pic_w) self.show_pic_w.show() self.show_pic_w.raise_() if self.show_pic_c: self.show_pic_c.close() self.show_pic_c = None if self.pic_c: self.show_pic_c = QLabel(self, Qt.Window) #self.show_pic_c.setAttribute(Qt.WA_DeleteOnClose) self.show_pic_c.setPicture(self.pic_c) self.show_pic_c.show() self.show_pic_c.raise_() log_debug("Received image") @property def image_list(self): '''List of images to paint''' return tuple(self._image_list) @image_list.setter def image_list(self, value): value = list(value) if self._image_list != value: self._image_list = value self.ui.imageList.clear() for img in value: self.ui.imageList.addItem(img) self.ui.autoUpdate.setEnabled(True) self.ui.zoomIn.setEnabled(True) self.ui.zoomOut.setEnabled(True) self.ui.zoom1.setEnabled(True) self.ui.zoomFit.setEnabled(True) self.ui.imageList.setEnabled(True) @pyqtSignature("") def on_zoomIn_clicked(self): self.ui.imageView.scale(2,2) @pyqtSignature("") def on_zoomOut_clicked(self): self.ui.imageView.scale(.5,.5) @pyqtSignature("") def on_zoom1_clicked(self): self.ui.imageView.setTransform(QTransform()) @pyqtSignature("") def on_zoomFit_clicked(self): self.ui.imageView.fitInView(QRectF(self.pix.rect()), Qt.KeepAspectRatio) @pyqtSignature("") def request_render_image(self): if self.isVisible(): if parameters.instance.use_thread: self.timer.start() else: self.render_image() @pyqtSignature("") def render_image(self): if self.isVisible(): i = self.ui.imageList.currentIndex() log_debug("Launch computing for image %d" % i) if self.thread.render_single(i) is None: QMessageBox.information(self, "Failed rendering image", "The renderer is busy and could not render the image.\nTry again later") else: self.ui.imageView.setEnabled(False) @pyqtSignature("int") def on_imageList_currentIndexChanged(self, value): self.render_image() @pyqtSignature("bool") def on_autoUpdate_toggled(self, value): self.parent.auto_update = value if value: self.request_render_image() def showEvent(self, event): self.render_image() def reject(self): self.close() def closeEvent(self, event): self.parent.preview_button.setChecked(False) if self.show_pic_w is not None: self.show_pic_w.close() self.show_pic_w = None if self.show_pic_c is not None: self.show_pic_c.close() self.show_pic_c = None
class Level(QFrame): def __init__(self, parent): QFrame.__init__(self,parent) self.filename = QString() self.copiedItem = QByteArray() self.pasteOffset = 5 self.prevPoint = QPoint() self.addOffset = 5 self.screenSize = (320, 240) self.bgColor = QColor(244,244,244) '''0.Portrait 1.Landscape''' self.orientation = 0 self.currentItem = None self.printer = QPrinter(QPrinter.HighResolution) self.printer.setPageSize(QPrinter.Letter) '''1.Header''' self.levelBar = LevelBar(self) '''3.Tiler''' self.tiler = TileMapGrid(self)#Tiler(self) self.tiler.setMinimumHeight(100) #self.tiler.currentChanged.connect(self.closeDesigner) #self.tiler.setTabsClosable(True) #self.tiler.setTabShape(0) #self.tiler.hide() #self.levelLayout.addWidget(self.levelBar) '''2.view''' viewLayoutWidget = QFrame() viewLayoutWidget.setFrameShape(QFrame.StyledPanel) viewLayout = QHBoxLayout(viewLayoutWidget) #viewLayout.setMargin(10) self.view = LevelView(viewLayoutWidget) '''scene''' self.scene = QGraphicsScene(self) #self.scene.selectionChanged.connect(self.setConnect) #self.view.setStyleSheet("border: 1px solid red;") self.setBackgroundColor(self.bgColor) self.setScreenSize(self.screenSize) self.view.setScene(self.scene) self.view.setAlignment(Qt.AlignCenter) self.scroll_off = 1 self.setScrollBar() viewLayout.setMargin(0) viewLayout.addWidget(self.view) self.wrapped = [] # Needed to keep wrappers alive layout = QVBoxLayout(self) layout.addWidget(self.levelBar) layout.addWidget(viewLayoutWidget) layout.addWidget(self.tiler) layout.setMargin(0) self.setLayout(layout) def setScrollBar(self): if(self.scroll_off): self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scroll_off = 0 else: self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.view.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.scroll_off = 1 def setBackgroundColor(self,color): self.bgColor = color self.scene.setBackgroundBrush(QBrush(color)) def setScreenSize(self,size): self.screenSize = size self.setOrientation(self.orientation) def setOrientation(self,idx): self.orientation = idx if(idx == 0): self.view.setMaximumSize(self.screenSize[1], self.screenSize[0]) self.scene.setSceneRect(0, 0, self.screenSize[1], self.screenSize[0]) else: self.view.setMaximumSize(self.screenSize[0], self.screenSize[1]) self.scene.setSceneRect(0, 0, self.screenSize[0], self.screenSize[1]) def offerSave(self): if (Dirty and QMessageBox.question(self, "Designer - Unsaved Changes", "Save unsaved changes?", QMessageBox.Yes|QMessageBox.No) == QMessageBox.Yes): self.save() def position(self): point = self.mapFromGlobal(QCursor.pos()) if not self.view.geometry().contains(point): coord = random.randint(36, 144) point = QPoint(coord, coord) else: if point == self.prevPoint: point += QPoint(self.addOffset, self.addOffset) self.addOffset += 5 else: self.addOffset = 5 self.prevPoint = point return self.view.mapToScene(point) def selectedItem(self): items = self.scene.selectedItems() if len(items) == 1: return items[0] return None def current(self,item): self.scene.clearSelection() sceneItems = self.scene.items() for items in sceneItems: if(items != item): item.setSelected(False) if(item.isConnected()): self.propertyBar.disconnectText(item) item.setConnected(False) self.currentItem = item self.currentItem.setConnected(True) self.currentItem.setSelected(True) self.propertyBar.connectText(self.currentItem) self.propertyBar.initText(self.currentItem) def addText(self): item = TextItem("SomeText", self.position()) self.connect(item, SIGNAL("current"),self.current) self.connect(item, SIGNAL("copy"),self.copy) self.connect(item, SIGNAL("cut"),self.cut) self.connect(item, SIGNAL("paste"),self.paste) self.connect(item, SIGNAL("delete"),self.delete) #self.current(item) self.scene.addItem(item) def copy(self): item = self.selectedItem() if item is None: return self.copiedItem.clear() self.pasteOffset = 5 stream = QDataStream(self.copiedItem, QIODevice.WriteOnly) self.writeItemToStream(stream, item) def cut(self): item = self.selectedItem() if item is None: return self.copy() self.scene.removeItem(item) del item def paste(self): if self.copiedItem.isEmpty(): return stream = QDataStream(self.copiedItem, QIODevice.ReadOnly) item = self.readItemFromStream(stream, self.pasteOffset) self.pasteOffset += 5 #self.scene.addItem(item) def delete(self): items = self.scene.selectedItems() if (len(items) and QMessageBox.question(self, "Designer - Delete", "Delete {0} item{1}?".format(len(items), "s" if len(items) != 1 else ""), QMessageBox.Yes|QMessageBox.No) == QMessageBox.Yes): while items: item = items.pop() self.scene.removeItem(item) del item def readItemFromStream(self, stream, offset=0): type = QString() position = QPointF() matrix = QMatrix() stream >> type >> position >> matrix if offset: position += QPointF(offset, offset) if type == "Text": text = QString() font = QFont() stream >> text >> font self.scene.addItem(TextItem(text, position, font, matrix)) elif type == "Box": rect = QRectF() stream >> rect style = Qt.PenStyle(stream.readInt16()) self.scene.addItem(BoxItem(position, style, matrix)) elif type == "Pixmap": pixmap = QPixmap() stream >> pixmap self.scene.addItem(self.createPixmapItem(pixmap, position, matrix)) def writeItemToStream(self, stream, item): if isinstance(item, QGraphicsTextItem): stream << QString("Text") << item.pos() \ << item.matrix() << item.toPlainText() << item.font() elif isinstance(item, QGraphicsPixmapItem): stream << QString("Pixmap") << item.pos() \ << item.matrix() << item.pixmap() elif isinstance(item, BoxItem): stream << QString("Box") << item.pos() \ << item.matrix() << item.rect stream.writeInt16(item.style) def rotate(self): for item in self.scene.selectedItems(): item.rotate(30) def print_(self): dialog = QPrintDialog(self.printer) if dialog.exec_(): painter = QPainter(self.printer) painter.setRenderHint(QPainter.Antialiasing) painter.setRenderHint(QPainter.TextAntialiasing) self.scene.clearSelection() #self.removeBorders() self.scene.render(painter) #self.addBorders() def open(self): self.offerSave() path = (QFileInfo(self.filename).path() if not self.filename.isEmpty() else ".") fname = QFileDialog.getOpenFileName(self, "Page Designer - Open", path, "Page Designer Files (*.pgd)") if fname.isEmpty(): return self.filename = fname fh = None try: fh = QFile(self.filename) if not fh.open(QIODevice.ReadOnly): raise IOError, unicode(fh.errorString()) items = self.scene.items() while items: item = items.pop() self.scene.removeItem(item) del item self.addBorders() stream = QDataStream(fh) stream.setVersion(QDataStream.Qt_4_2) magic = stream.readInt32() if magic != MagicNumber: raise IOError, "not a valid .pgd file" fileVersion = stream.readInt16() if fileVersion != FileVersion: raise IOError, "unrecognised .pgd file version" while not fh.atEnd(): self.readItemFromStream(stream) except IOError, e: QMessageBox.warning(self, "Page Designer -- Open Error", "Failed to open {0}: {1}".format(self.filename, e)) finally:
class OWSieveDiagram(OWWidget): name = "Sieve Diagram" description = "A two-way contingency table providing information on the " \ "relation between the observed and expected frequencies " \ "of a combination of feature values under the assumption of independence." icon = "icons/SieveDiagram.svg" priority = 4200 inputs = [("Data", Table, "set_data", Default), ("Features", AttributeList, "set_input_features")] outputs = [("Selection", Table)] graph_name = "canvas" want_control_area = False settingsHandler = DomainContextHandler() attrX = ContextSetting("") attrY = ContextSetting("") selection = ContextSetting(set()) def __init__(self): super().__init__() self.data = None self.input_features = None self.attrs = [] self.attr_box = gui.hBox(self.mainArea) model = VariableListModel() model.wrap(self.attrs) self.attrXCombo = gui.comboBox( self.attr_box, self, value="attrX", contentsLength=12, callback=self.change_attr, sendSelectedValue=True, valueType=str) self.attrXCombo.setModel(model) gui.widgetLabel(self.attr_box, "\u2715").\ setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.attrYCombo = gui.comboBox( self.attr_box, self, value="attrY", contentsLength=12, callback=self.change_attr, sendSelectedValue=True, valueType=str) self.attrYCombo.setModel(model) self.canvas = QGraphicsScene() self.canvasView = ViewWithPress(self.canvas, self.mainArea, handler=self.reset_selection) self.mainArea.layout().addWidget(self.canvasView) self.canvasView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.canvasView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) box = gui.hBox(self.mainArea) box.layout().addWidget(self.graphButton) box.layout().addWidget(self.report_button) def sizeHint(self): return QSize(450, 550) def set_data(self, data): if type(data) == SqlTable and data.approx_len() > LARGE_TABLE: data = data.sample_time(DEFAULT_SAMPLE_TIME) self.closeContext() self.data = data self.areas = [] if self.data is None: self.attrs[:] = [] else: self.attrs[:] = [ var for var in chain(self.data.domain, self.data.domain.metas) if var.is_discrete ] if self.attrs: self.attrX = self.attrs[0].name self.attrY = self.attrs[len(self.attrs) > 1].name else: self.attrX = self.attrY = None self.openContext(self.data) self.information(0, "") if data and any(attr.is_continuous for attr in data.domain): self.information(0, "Data contains continuous variables. " "Discretize the data to use them.") self.resolve_shown_attributes() self.update_selection() def change_attr(self): self.selection = set() self.updateGraph() self.update_selection() def set_input_features(self, attrList): self.input_features = attrList self.resolve_shown_attributes() self.update_selection() def resolve_shown_attributes(self): self.warning(1) self.attr_box.setEnabled(True) if self.input_features: # non-None and non-empty! features = [f for f in self.input_features if f in self.attrs] if not features: self.warning(1, "Features from the input signal " "are not present in the data") else: old_attrs = self.attrX, self.attrY self.attrX, self.attrY = [f.name for f in (features * 2)[:2]] self.attr_box.setEnabled(False) if (self.attrX, self.attrY) != old_attrs: self.selection = set() # else: do nothing; keep current features, even if input with the # features just changed to None self.updateGraph() def resizeEvent(self, e): OWWidget.resizeEvent(self,e) self.updateGraph() def showEvent(self, ev): OWWidget.showEvent(self, ev) self.updateGraph() def reset_selection(self): self.selection = set() self.update_selection() def select_area(self, area, ev): if ev.button() != Qt.LeftButton: return index = self.areas.index(area) if ev.modifiers() & Qt.ControlModifier: self.selection ^= {index} else: self.selection = {index} self.update_selection() def update_selection(self): if self.areas is None or not self.selection: self.send("Selection", None) return filters = [] for i, area in enumerate(self.areas): if i in self.selection: width = 4 val_x, val_y = area.value_pair filters.append( filter.Values([ filter.FilterDiscrete(self.attrX, [val_x]), filter.FilterDiscrete(self.attrY, [val_y]) ])) else: width = 1 pen = area.pen() pen.setWidth(width) area.setPen(pen) if len(filters) == 1: filters = filters[0] else: filters = filter.Values(filters, conjunction=False) self.send("Selection", filters(self.data)) # ----------------------------------------------------------------------- # Everything from here on is ancient and has been changed only according # to what has been changed above. Some clean-up may be in order some day # def updateGraph(self, *args): for item in self.canvas.items(): self.canvas.removeItem(item) if self.data is None or len(self.data) == 0 or \ self.attrX is None or self.attrY is None: return data = self.data[:, [self.attrX, self.attrY]] valsX = [] valsY = [] contX = get_contingency(data, self.attrX, self.attrX) contY = get_contingency(data, self.attrY, self.attrY) # compute contingency of x and y attributes for entry in contX: sum_ = 0 try: for val in entry: sum_ += val except: pass valsX.append(sum_) for entry in contY: sum_ = 0 try: for val in entry: sum_ += val except: pass valsY.append(sum_) contXY, _ = get_conditional_distribution( data, [data.domain[self.attrX], data.domain[self.attrY]]) # compute probabilities probs = {} for i in range(len(valsX)): valx = valsX[i] for j in range(len(valsY)): valy = valsY[j] try: actualProb = contXY['%s-%s' %(data.domain[self.attrX].values[i], data.domain[self.attrY].values[j])] # for val in contXY['%s-%s' %(i, j)]: actualProb += val except: actualProb = 0 probs['%s-%s' %(data.domain[self.attrX].values[i], data.domain[self.attrY].values[j])] = ((data.domain[self.attrX].values[i], valx), (data.domain[self.attrY].values[j], valy), actualProb, len(data)) #get text width of Y labels max_ylabel_w = 0 for j in range(len(valsY)): xl = CanvasText(self.canvas, "", 0, 0, html_text= getHtmlCompatibleString(data.domain[self.attrY].values[j]), show=False) max_ylabel_w = max(int(xl.boundingRect().width()), max_ylabel_w) max_ylabel_w = min(max_ylabel_w, 200) #upper limit for label widths # get text width of Y attribute name text = CanvasText(self.canvas, data.domain[self.attrY].name, x = 0, y = 0, bold = 1, show = 0, vertical=True) xOff = int(text.boundingRect().height() + max_ylabel_w) yOff = 55 sqareSize = min(self.canvasView.width() - xOff - 35, self.canvasView.height() - yOff - 50) sqareSize = max(sqareSize, 10) self.canvasView.setSceneRect(0, 0, self.canvasView.width(), self.canvasView.height()) # print graph name name = "<b>P(%s, %s) ≠ P(%s)×P(%s)</b>" %(self.attrX, self.attrY, self.attrX, self.attrY) CanvasText(self.canvas, "", xOff + sqareSize / 2, 20, Qt.AlignCenter, html_text= name) CanvasText(self.canvas, "N = " + str(len(data)), xOff + sqareSize / 2, 38, Qt.AlignCenter, bold = 0) ###################### # compute chi-square chisquare = 0.0 for i in range(len(valsX)): for j in range(len(valsY)): ((xAttr, xVal), (yAttr, yVal), actual, sum_) = probs['%s-%s' %(data.domain[self.attrX].values[i], data.domain[self.attrY].values[j])] expected = float(xVal*yVal)/float(sum_) if expected == 0: continue pearson2 = (actual - expected)*(actual - expected) / expected chisquare += pearson2 ###################### # draw rectangles currX = xOff max_xlabel_h = 0 normX, normY = sum(valsX), sum(valsY) self.areas = [] for i in range(len(valsX)): if valsX[i] == 0: continue currY = yOff width = int(float(sqareSize * valsX[i])/float(normX)) for j in range(len(valsY)-1, -1, -1): # this way we sort y values correctly ((xAttr, xVal), (yAttr, yVal), actual, sum_) = probs['%s-%s' %(data.domain[self.attrX].values[i], data.domain[self.attrY].values[j])] if valsY[j] == 0: continue height = int(float(sqareSize * valsY[j])/float(normY)) # create rectangle selected = len(self.areas) in self.selection rect = CanvasRectangle( self.canvas, currX+2, currY+2, width-4, height-4, z = -10, onclick=self.select_area) rect.value_pair = i, j self.areas.append(rect) self.addRectIndependencePearson(rect, currX+2, currY+2, width-4, height-4, (xAttr, xVal), (yAttr, yVal), actual, sum_, width=1 + 3 * selected, # Ugly! This is needed since # resize redraws the graph! When this is handled by resizing # just the viewer, update_selection will take care of this ) expected = float(xVal*yVal)/float(sum_) pearson = (actual - expected) / sqrt(expected) tooltipText = """<b>X Attribute: %s</b><br>Value: <b>%s</b><br>Number of instances (p(x)): <b>%d (%.2f%%)</b><hr> <b>Y Attribute: %s</b><br>Value: <b>%s</b><br>Number of instances (p(y)): <b>%d (%.2f%%)</b><hr> <b>Number Of Instances (Probabilities):</b><br>Expected (p(x)p(y)): <b>%.1f (%.2f%%)</b><br>Actual (p(x,y)): <b>%d (%.2f%%)</b> <hr><b>Statistics:</b><br>Chi-square: <b>%.2f</b><br>Standardized Pearson residual: <b>%.2f</b>""" %(self.attrX, getHtmlCompatibleString(xAttr), xVal, 100.0*float(xVal)/float(sum_), self.attrY, getHtmlCompatibleString(yAttr), yVal, 100.0*float(yVal)/float(sum_), expected, 100.0*float(xVal*yVal)/float(sum_*sum_), actual, 100.0*float(actual)/float(sum_), chisquare, pearson ) rect.setToolTip(tooltipText) currY += height if currX == xOff: CanvasText(self.canvas, "", xOff, currY - height / 2, Qt.AlignRight | Qt.AlignVCenter, html_text= getHtmlCompatibleString(data.domain[self.attrY].values[j])) xl = CanvasText(self.canvas, "", currX + width / 2, yOff + sqareSize, Qt.AlignHCenter | Qt.AlignTop, html_text= getHtmlCompatibleString(data.domain[self.attrX].values[i])) max_xlabel_h = max(int(xl.boundingRect().height()), max_xlabel_h) currX += width # show attribute names CanvasText(self.canvas, self.attrY, 0, yOff + sqareSize / 2, Qt.AlignLeft | Qt.AlignVCenter, bold = 1, vertical=True) CanvasText(self.canvas, self.attrX, xOff + sqareSize / 2, yOff + sqareSize + max_xlabel_h, Qt.AlignHCenter | Qt.AlignTop, bold = 1) ###################################################################### ## show deviations from attribute independence with standardized pearson residuals def addRectIndependencePearson(self, rect, x, y, w, h, xAttr_xVal, yAttr_yVal, actual, sum, width): xAttr, xVal = xAttr_xVal yAttr, yVal = yAttr_yVal expected = float(xVal*yVal)/float(sum) pearson = (actual - expected) / sqrt(expected) if pearson > 0: # if there are more examples that we would expect under the null hypothesis intPearson = floor(pearson) pen = QPen(QColor(0,0,255), width); rect.setPen(pen) b = 255 r = g = 255 - intPearson*20 r = g = max(r, 55) # elif pearson < 0: intPearson = ceil(pearson) pen = QPen(QColor(255,0,0), width) rect.setPen(pen) r = 255 b = g = 255 + intPearson*20 b = g = max(b, 55) else: pen = QPen(QColor(255,255,255), width) r = g = b = 255 # white color = QColor(r,g,b) brush = QBrush(color) rect.setBrush(brush) if pearson > 0: pearson = min(pearson, 10) kvoc = 1 - 0.08 * pearson # if pearson in [0..10] --> kvoc in [1..0.2] else: pearson = max(pearson, -10) kvoc = 1 - 0.4*pearson pen.setWidth(1) self.addLines(x,y,w,h, kvoc, pen) ################################################## # add lines def addLines(self, x, y, w, h, diff, pen): if w == 0 or h == 0: return dist = 20 * diff # original distance between two lines in pixels temp = dist canvas = self.canvas while temp < w: r = QGraphicsLineItem(temp + x, y, temp + x, y + h, None) canvas.addItem(r) r.setPen(pen) temp += dist temp = dist while temp < h: r = QGraphicsLineItem(x, y + temp, x + w, y + temp, None) canvas.addItem(r) r.setPen(pen) temp += dist def closeEvent(self, ce): QDialog.closeEvent(self, ce) def get_widget_name_extension(self): if self.data is not None: return "{} vs {}".format(self.attrX, self.attrY) def send_report(self): self.report_plot()
class QtImageViewer(QGraphicsView): """ PyQt image viewer widget for a QPixmap in a QGraphicsView scene with mouse zooming and panning. Displays a QImage or QPixmap (QImage is internally converted to a QPixmap). To display any other image format, you must first convert it to a QImage or QPixmap. Some useful image format conversion utilities: qimage2ndarray: NumPy ndarray <==> QImage (https://github.com/hmeine/qimage2ndarray) ImageQt: PIL Image <==> QImage (https://github.com/python-pillow/Pillow/blob/master/PIL/ImageQt.py) """ # Mouse button signals emit image scene (x, y) coordinates. # !!! For image (row, column) matrix indexing, row = y and column = x. hover = pyqtSignal(float, float) leftMouseButtonPressed = pyqtSignal(float, float, object) rightMouseButtonPressed = pyqtSignal(float, float) leftMouseButtonReleased = pyqtSignal(float, float) leftMouseButtonDoubleClicked = pyqtSignal(float, float) rightMouseButtonDoubleClicked = pyqtSignal(float, float) areaSelected = pyqtSignal(float, float, float, float) pixelSelected = pyqtSignal(float, float) def __init__(self, parent=None, center=None, thumbnail=False): QGraphicsView.__init__(self, parent) # Image is displayed as a QPixmap in a QGraphicsScene attached to this QGraphicsView. self.scene = QGraphicsScene() self.setScene(self.scene) # Store a local handle to the scene's current image pixmap. self._pixmapHandle = None # Image aspect ratio mode. # !!! ONLY applies to full image. Aspect ratio is always ignored when zooming. # Qt.IgnoreAspectRatio: Scale image to fit viewport. # Qt.KeepAspectRatio: Scale image to fit inside viewport, preserving aspect ratio. # Qt.KeepAspectRatioByExpanding: Scale image to fill the viewport, preserving aspect ratio. self.aspectRatioMode = Qt.KeepAspectRatio # Scroll bar behaviour. # Qt.ScrollBarAlwaysOff: Never shows a scroll bar. # Qt.ScrollBarAlwaysOn: Always shows a scroll bar. # Qt.ScrollBarAsNeeded: Shows a scroll bar only when zoomed. self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) # Stack of QRectF zoom boxes in scene coordinates. self.zoomStack = [] self.dragSelect = False # Flags for enabling/disabling mouse interaction. self.canZoom = True self.canPan = True self.center = center self.thumbnail = thumbnail def hasImage(self): """ Returns whether or not the scene contains an image pixmap. """ return self._pixmapHandle is not None def clearImage(self): """ Removes the current image pixmap from the scene if it exists. """ if self.hasImage(): self.scene.removeItem(self._pixmapHandle) self.scene.clear() self._pixmapHandle = None def pixmap(self): """ Returns the scene's current image pixmap as a QPixmap, or else None if no image exists. :rtype: QPixmap | None """ if self.hasImage(): return self._pixmapHandle.pixmap() return None def image(self): """ Returns the scene's current image pixmap as a QImage, or else None if no image exists. :rtype: QImage | None """ if self.hasImage(): return self._pixmapHandle.pixmap().toImage() return None def setImage(self, image): """ Set the scene's current image pixmap to the input QImage or QPixmap. Raises a RuntimeError if the input image has type other than QImage or QPixmap. :type image: QImage | QPixmap """ if type(image) is QPixmap: pixmap = image elif type(image) is QImage: pixmap = QPixmap.fromImage(image) else: raise RuntimeError( "ImageViewer.setImage: Argument must be a QImage or QPixmap.") if self.hasImage(): self._pixmapHandle.setPixmap(pixmap) else: self._pixmapHandle = self.scene.addPixmap(pixmap) self.setSceneRect(QRectF( pixmap.rect())) # Set scene size to image size. self.updateViewer() def loadImageFromFile(self, fileName=""): """ Load an image from file. Without any arguments, loadImageFromFile() will popup a file dialog to choose the image file. With a fileName argument, loadImageFromFile(fileName) will attempt to load the specified image file directly. """ if len(fileName) == 0: if QT_VERSION_STR[0] == '4': fileName = QFileDialog.getOpenFileName(self, "Open image file.") elif QT_VERSION_STR[0] == '5': fileName, dummy = QFileDialog.getOpenFileName( self, "Open image file.") if len(fileName) and os.path.isfile(fileName): image = QImage(fileName) self.setImage(image) def updateViewer(self): """ Show current zoom (if showing entire image, apply current aspect ratio mode). """ if not self.hasImage(): return if len(self.zoomStack) and self.sceneRect().contains( self.zoomStack[-1]): self.fitInView(self.zoomStack[-1], Qt.IgnoreAspectRatio ) # Show zoomed rect (ignore aspect ratio). else: self.zoomStack = [ ] # Clear the zoom stack (in case we got here because of an invalid zoom). if self.thumbnail == True: self.fitInView( self.sceneRect(), self.aspectRatioMode ) # Show entire image (use current aspect ratio mode). def resizeEvent(self, event): """ Maintain current zoom on resize. """ self.updateViewer() def mouseMoveEvent(self, event): if self.hasImage(): scenePos = self.mapToScene(event.pos()) self.hover.emit(scenePos.x(), scenePos.y()) if event.buttons() == Qt.RightButton: self.rightMouseButtonPressed.emit(scenePos.x(), scenePos.y()) else: pass QGraphicsView.mouseMoveEvent(self, event) def mousePressEvent(self, event): """ Start mouse pan or zoom mode. """ scenePos = self.mapToScene(event.pos()) if event.button() == Qt.LeftButton: if self.canPan: self.setDragMode(QGraphicsView.ScrollHandDrag) if self.hasImage(): self.leftMouseButtonPressed.emit(scenePos.x(), scenePos.y(), self) elif event.button() == Qt.RightButton: if self.dragSelect: if self.hasImage(): self.setDragMode(QGraphicsView.RubberBandDrag) self.viewport().setCursor(QCursor(Qt.CrossCursor)) self.rightMouseButtonPressed.emit(scenePos.x(), scenePos.y()) QGraphicsView.mousePressEvent(self, event) def mouseReleaseEvent(self, event): """ Stop mouse pan or zoom mode (apply zoom if valid). """ QGraphicsView.mouseReleaseEvent(self, event) scenePos = self.mapToScene(event.pos()) if event.button() == Qt.LeftButton: self.setDragMode(QGraphicsView.NoDrag) if self.hasImage(): self.leftMouseButtonReleased.emit(scenePos.x(), scenePos.y()) elif event.button() == Qt.RightButton: self.setDragMode(QGraphicsView.NoDrag) if self.hasImage(): self.viewport().setCursor(QCursor(Qt.OpenHandCursor)) if self.dragSelect: viewBBox = self.zoomStack[-1] if len( self.zoomStack) else self.sceneRect() selectionBBox = self.scene.selectionArea().boundingRect( ).intersected(viewBBox) print(selectionBBox) self.areaSelected.emit(selectionBBox.x(), selectionBBox.y(), selectionBBox.width(), selectionBBox.height()) else: self.pixelSelected.emit(scenePos.x(), scenePos.y()) def mouseDoubleClickEvent(self, event): """ Show entire image. """ scenePos = self.mapToScene(event.pos()) if event.button() == Qt.LeftButton: self.leftMouseButtonDoubleClicked.emit(scenePos.x(), scenePos.y()) elif event.button() == Qt.RightButton: if self.canZoom: self.zoomStack = [] # Clear zoom stack. self.updateViewer() self.rightMouseButtonDoubleClicked.emit(scenePos.x(), scenePos.y()) QGraphicsView.mouseDoubleClickEvent(self, event) def wheelEvent(self, event): try: if self.thumbnail == True: return zoomInFactor = 1.2 zoomOutFactor = .8 # Zoom if event.angleDelta().y() > 0: zoomFactor = zoomInFactor else: zoomFactor = zoomOutFactor self.scale(zoomFactor, zoomFactor) # Get the new position newPos = self.mapToScene(event.pos()) self.centerOn(newPos) except Exception as e: logging.info(e) def zoomToArea(self, center, scale): if self.hasImage(): self.setTransform(QTransform()) pos = QPoint(center[0], center[1]) self.centerOn(pos) self.scale(scale, scale) def resetZoom(self): self.setTransform(QTransform()) self.fitInView(self.sceneRect(), self.aspectRatioMode ) # Show entire image (use current aspect ratio mode). def showEvent(self, event): self.resetZoom()
class OWSieveDiagram(OWWidget): name = "Sieve Diagram" description = "A two-way contingency table providing information in " \ "relation to expected frequency of combination of feature " \ "values under independence." icon = "icons/SieveDiagram.svg" priority = 4200 inputs = [("Data", Table, "set_data", Default), ("Features", AttributeList, "set_input_features")] outputs = [("Selection", Table)] graph_name = "canvas" want_control_area = False settingsHandler = DomainContextHandler() attrX = ContextSetting("") attrY = ContextSetting("") selection = ContextSetting(set()) def __init__(self): super().__init__() self.data = None self.input_features = None self.attrs = [] self.attr_box = gui.hBox(self.mainArea) model = VariableListModel() model.wrap(self.attrs) self.attrXCombo = gui.comboBox(self.attr_box, self, value="attrX", contentsLength=12, callback=self.change_attr, sendSelectedValue=True, valueType=str) self.attrXCombo.setModel(model) gui.widgetLabel(self.attr_box, "\u2715").\ setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.attrYCombo = gui.comboBox(self.attr_box, self, value="attrY", contentsLength=12, callback=self.change_attr, sendSelectedValue=True, valueType=str) self.attrYCombo.setModel(model) self.canvas = QGraphicsScene() self.canvasView = ViewWithPress(self.canvas, self.mainArea, handler=self.reset_selection) self.mainArea.layout().addWidget(self.canvasView) self.canvasView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.canvasView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) box = gui.hBox(self.mainArea) gui.button(box, None, "&Save Graph", callback=self.save_graph, autoDefault=False) gui.button(box, None, "&Report", callback=self.show_report, autoDefault=False) def sizeHint(self): return QSize(450, 550) def set_data(self, data): if type(data) == SqlTable and data.approx_len() > LARGE_TABLE: data = data.sample_time(DEFAULT_SAMPLE_TIME) self.closeContext() self.data = data self.areas = [] if self.data is None: self.attrs[:] = [] else: self.attrs[:] = [ var for var in chain(self.data.domain, self.data.domain.metas) if var.is_discrete ] if self.attrs: self.attrX = self.attrs[0].name self.attrY = self.attrs[len(self.attrs) > 1].name else: self.attrX = self.attrY = None self.openContext(self.data) self.information(0, "") if data and any(attr.is_continuous for attr in data.domain): self.information( 0, "Data contains continuous variables. " "Discretize the data to use them.") self.resolve_shown_attributes() self.update_selection() def change_attr(self): self.selection = set() self.updateGraph() self.update_selection() def set_input_features(self, attrList): self.input_features = attrList self.resolve_shown_attributes() self.update_selection() def resolve_shown_attributes(self): self.warning(1) self.attr_box.setEnabled(True) if self.input_features: # non-None and non-empty! features = [f for f in self.input_features if f in self.attrs] if not features: self.warning( 1, "Features from the input signal " "are not present in the data") else: old_attrs = self.attrX, self.attrY self.attrX, self.attrY = [f.name for f in (features * 2)[:2]] self.attr_box.setEnabled(False) if (self.attrX, self.attrY) != old_attrs: self.selection = set() # else: do nothing; keep current features, even if input with the # features just changed to None self.updateGraph() def resizeEvent(self, e): OWWidget.resizeEvent(self, e) self.updateGraph() def showEvent(self, ev): OWWidget.showEvent(self, ev) self.updateGraph() def reset_selection(self): self.selection = set() self.update_selection() def select_area(self, area, ev): if ev.button() != Qt.LeftButton: return index = self.areas.index(area) if ev.modifiers() & Qt.ControlModifier: self.selection ^= {index} else: self.selection = {index} self.update_selection() def update_selection(self): if self.areas is None or not self.selection: self.send("Selection", None) return filters = [] for i, area in enumerate(self.areas): if i in self.selection: width = 4 val_x, val_y = area.value_pair filters.append( filter.Values([ filter.FilterDiscrete(self.attrX, [val_x]), filter.FilterDiscrete(self.attrY, [val_y]) ])) else: width = 1 pen = area.pen() pen.setWidth(width) area.setPen(pen) if len(filters) == 1: filters = filters[0] else: filters = filter.Values(filters, conjunction=False) self.send("Selection", filters(self.data)) # ----------------------------------------------------------------------- # Everything from here on is ancient and has been changed only according # to what has been changed above. Some clean-up may be in order some day # def updateGraph(self, *args): for item in self.canvas.items(): self.canvas.removeItem(item) if self.data is None or len(self.data) == 0 or \ self.attrX is None or self.attrY is None: return data = self.data[:, [self.attrX, self.attrY]] valsX = [] valsY = [] contX = get_contingency(data, self.attrX, self.attrX) contY = get_contingency(data, self.attrY, self.attrY) # compute contingency of x and y attributes for entry in contX: sum_ = 0 try: for val in entry: sum_ += val except: pass valsX.append(sum_) for entry in contY: sum_ = 0 try: for val in entry: sum_ += val except: pass valsY.append(sum_) contXY, _ = get_conditional_distribution( data, [data.domain[self.attrX], data.domain[self.attrY]]) # compute probabilities probs = {} for i in range(len(valsX)): valx = valsX[i] for j in range(len(valsY)): valy = valsY[j] try: actualProb = contXY['%s-%s' % (data.domain[self.attrX].values[i], data.domain[self.attrY].values[j])] # for val in contXY['%s-%s' %(i, j)]: actualProb += val except: actualProb = 0 probs['%s-%s' % (data.domain[self.attrX].values[i], data.domain[self.attrY].values[j])] = (( data.domain[self.attrX].values[i], valx), (data.domain[self.attrY].values[j], valy), actualProb, len(data)) #get text width of Y labels max_ylabel_w = 0 for j in range(len(valsY)): xl = CanvasText(self.canvas, "", 0, 0, html_text=getHtmlCompatibleString( data.domain[self.attrY].values[j]), show=False) max_ylabel_w = max(int(xl.boundingRect().width()), max_ylabel_w) max_ylabel_w = min(max_ylabel_w, 200) #upper limit for label widths # get text width of Y attribute name text = CanvasText(self.canvas, data.domain[self.attrY].name, x=0, y=0, bold=1, show=0, vertical=True) xOff = int(text.boundingRect().height() + max_ylabel_w) yOff = 55 sqareSize = min(self.canvasView.width() - xOff - 35, self.canvasView.height() - yOff - 50) sqareSize = max(sqareSize, 10) self.canvasView.setSceneRect(0, 0, self.canvasView.width(), self.canvasView.height()) # print graph name name = "<b>P(%s, %s) ≠ P(%s)×P(%s)</b>" % ( self.attrX, self.attrY, self.attrX, self.attrY) CanvasText(self.canvas, "", xOff + sqareSize / 2, 20, Qt.AlignCenter, html_text=name) CanvasText(self.canvas, "N = " + str(len(data)), xOff + sqareSize / 2, 38, Qt.AlignCenter, bold=0) ###################### # compute chi-square chisquare = 0.0 for i in range(len(valsX)): for j in range(len(valsY)): ((xAttr, xVal), (yAttr, yVal), actual, sum_) = probs['%s-%s' % (data.domain[self.attrX].values[i], data.domain[self.attrY].values[j])] expected = float(xVal * yVal) / float(sum_) if expected == 0: continue pearson2 = (actual - expected) * (actual - expected) / expected chisquare += pearson2 ###################### # draw rectangles currX = xOff max_xlabel_h = 0 normX, normY = sum(valsX), sum(valsY) self.areas = [] for i in range(len(valsX)): if valsX[i] == 0: continue currY = yOff width = int(float(sqareSize * valsX[i]) / float(normX)) for j in range(len(valsY) - 1, -1, -1): # this way we sort y values correctly ((xAttr, xVal), (yAttr, yVal), actual, sum_) = probs['%s-%s' % (data.domain[self.attrX].values[i], data.domain[self.attrY].values[j])] if valsY[j] == 0: continue height = int(float(sqareSize * valsY[j]) / float(normY)) # create rectangle selected = len(self.areas) in self.selection rect = CanvasRectangle(self.canvas, currX + 2, currY + 2, width - 4, height - 4, z=-10, onclick=self.select_area) rect.value_pair = i, j self.areas.append(rect) self.addRectIndependencePearson( rect, currX + 2, currY + 2, width - 4, height - 4, (xAttr, xVal), (yAttr, yVal), actual, sum_, width=1 + 3 * selected, # Ugly! This is needed since # resize redraws the graph! When this is handled by resizing # just the viewer, update_selection will take care of this ) expected = float(xVal * yVal) / float(sum_) pearson = (actual - expected) / sqrt(expected) tooltipText = """<b>X Attribute: %s</b><br>Value: <b>%s</b><br>Number of instances (p(x)): <b>%d (%.2f%%)</b><hr> <b>Y Attribute: %s</b><br>Value: <b>%s</b><br>Number of instances (p(y)): <b>%d (%.2f%%)</b><hr> <b>Number Of Instances (Probabilities):</b><br>Expected (p(x)p(y)): <b>%.1f (%.2f%%)</b><br>Actual (p(x,y)): <b>%d (%.2f%%)</b> <hr><b>Statistics:</b><br>Chi-square: <b>%.2f</b><br>Standardized Pearson residual: <b>%.2f</b>""" % ( self.attrX, getHtmlCompatibleString(xAttr), xVal, 100.0 * float(xVal) / float(sum_), self.attrY, getHtmlCompatibleString(yAttr), yVal, 100.0 * float(yVal) / float(sum_), expected, 100.0 * float(xVal * yVal) / float(sum_ * sum_), actual, 100.0 * float(actual) / float(sum_), chisquare, pearson) rect.setToolTip(tooltipText) currY += height if currX == xOff: CanvasText(self.canvas, "", xOff, currY - height / 2, Qt.AlignRight | Qt.AlignVCenter, html_text=getHtmlCompatibleString( data.domain[self.attrY].values[j])) xl = CanvasText(self.canvas, "", currX + width / 2, yOff + sqareSize, Qt.AlignHCenter | Qt.AlignTop, html_text=getHtmlCompatibleString( data.domain[self.attrX].values[i])) max_xlabel_h = max(int(xl.boundingRect().height()), max_xlabel_h) currX += width # show attribute names CanvasText(self.canvas, self.attrY, 0, yOff + sqareSize / 2, Qt.AlignLeft | Qt.AlignVCenter, bold=1, vertical=True) CanvasText(self.canvas, self.attrX, xOff + sqareSize / 2, yOff + sqareSize + max_xlabel_h, Qt.AlignHCenter | Qt.AlignTop, bold=1) ###################################################################### ## show deviations from attribute independence with standardized pearson residuals def addRectIndependencePearson(self, rect, x, y, w, h, xAttr_xVal, yAttr_yVal, actual, sum, width): xAttr, xVal = xAttr_xVal yAttr, yVal = yAttr_yVal expected = float(xVal * yVal) / float(sum) pearson = (actual - expected) / sqrt(expected) if pearson > 0: # if there are more examples that we would expect under the null hypothesis intPearson = floor(pearson) pen = QPen(QColor(0, 0, 255), width) rect.setPen(pen) b = 255 r = g = 255 - intPearson * 20 r = g = max(r, 55) # elif pearson < 0: intPearson = ceil(pearson) pen = QPen(QColor(255, 0, 0), width) rect.setPen(pen) r = 255 b = g = 255 + intPearson * 20 b = g = max(b, 55) else: pen = QPen(QColor(255, 255, 255), width) r = g = b = 255 # white color = QColor(r, g, b) brush = QBrush(color) rect.setBrush(brush) if pearson > 0: pearson = min(pearson, 10) kvoc = 1 - 0.08 * pearson # if pearson in [0..10] --> kvoc in [1..0.2] else: pearson = max(pearson, -10) kvoc = 1 - 0.4 * pearson pen.setWidth(1) self.addLines(x, y, w, h, kvoc, pen) ################################################## # add lines def addLines(self, x, y, w, h, diff, pen): if w == 0 or h == 0: return dist = 20 * diff # original distance between two lines in pixels temp = dist canvas = self.canvas while temp < w: r = QGraphicsLineItem(temp + x, y, temp + x, y + h, None) canvas.addItem(r) r.setPen(pen) temp += dist temp = dist while temp < h: r = QGraphicsLineItem(x, y + temp, x + w, y + temp, None) canvas.addItem(r) r.setPen(pen) temp += dist def closeEvent(self, ce): QDialog.closeEvent(self, ce) def get_widget_name_extension(self): if self.data is not None: return "{} vs {}".format(self.attrX, self.attrY) def send_report(self): self.report_plot()
class PaintArea(QGraphicsView): def __init__(self, parent): QGraphicsView.__init__(self) self.init(parent) self.initShape() self.initCall = 2 self.minimumWidth = 550 self.setEnabled(False) def init(self, parent): self.timeline = parent self.pixmap = None self.image = None def initShape(self): self.scene = QGraphicsScene() self.setScene(self.scene) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setAlignment(Qt.AlignLeft) self.clicStart = None self.selectionRect = None def resizeEvent(self, sizEvent): if self.selectionRect and self.scene: self.scene.removeItem(self.selectionRect) self.selectionRect = None self.clicStart = None self.width = sizEvent.size().width() self.height = sizEvent.size().height() if self.initCall > 0: self.timeline.options.setMaximumWidth( self.timeline.options.minimumSizeHint().width()) self.initCall -= 1 if self.width < self.minimumWidth: self.timeline.setStateInfo('Unable to draw - Not enought width') else: self.timeline.workerThread.render() def mousePressEvent(self, mouseEvent): self.clicStart = mouseEvent.pos() if self.clicStart.x() < self.timeline.draw.yLeftMargin - 1: self.clicStart.setX(self.timeline.draw.yLeftMargin - 1) if self.clicStart.x( ) > self.timeline.ploter.width - self.timeline.m + 1: self.clicStart.setX(self.timeline.ploter.width - self.timeline.m + 1) if self.selectionRect: for item in self.scene.items(): if str(type( item)) == "<class 'PyQt4.QtGui.QGraphicsRectItem'>": self.scene.removeItem(item) self.selectionRect = None self.timeline.options.zoomButton.setEnabled(False) self.timeline.options.exportButton.setEnabled(False) self.timeline.options.selectedNodes.setText('Nothing selected') if self.timeline.selDateMin: self.timeline.options.selStartTime.setText('From ' + str( self.timeline.fromUSec(self.timeline.baseDateMin).strftime( '%d.%m.%Y %H:%M:%S'))) self.timeline.options.selEndTime.setText('To ' + str( self.timeline.fromUSec(self.timeline.baseDateMax).strftime( '%d.%m.%Y %H:%M:%S'))) else: self.timeline.options.selStartTime.setText( 'No selection start time') self.timeline.options.selEndTime.setText( 'No selection end time') self.timeline.options.selectedNodes.setText('Nothing selected') def mouseMoveEvent(self, dragMoveEvent): if self.clicStart: if self.clicStart.x() < dragMoveEvent.x(): x = self.clicStart.x() w = dragMoveEvent.x() - self.clicStart.x() else: x = dragMoveEvent.x() w = self.clicStart.x() - dragMoveEvent.x() # Limit rectangle to selectable area if x < self.timeline.draw.yLeftMargin - 1: x = self.timeline.draw.yLeftMargin - 1 w = self.clicStart.x() - x if x > self.timeline.ploter.width - self.timeline.m + 1: x = self.timeline.ploter.width - self.timeline.m + 1 w = 0 if x + w > self.timeline.ploter.width - self.timeline.m + 1: w = ((self.timeline.ploter.width - self.timeline.m + 1) - x) y = (self.timeline.m / 3) - 1 h = self.height - self.timeline.m - self.timeline.m / 3 + 2 if self.selectionRect and self.scene: self.scene.removeItem(self.selectionRect) self.selectionRect = self.scene.addRect(QRectF(x, y, w, h), QPen(Qt.DashDotDotLine)) def mouseReleaseEvent(self, mouseEvent): if self.selectionRect: if self.clicStart.x() > mouseEvent.x(): x1 = mouseEvent.x() x2 = self.clicStart.x() else: x1 = self.clicStart.x() x2 = mouseEvent.x() self.timeline.nodesInRange(x1, x2) start = self.timeline.draw.findXTime(x1) if start: self.timeline.options.selStartTime.setText( 'From ' + str(start.strftime('%d.%m.%Y %H:%M:%S'))) end = self.timeline.draw.findXTime(x2) if end: self.timeline.options.selEndTime.setText( 'To ' + str(end.strftime('%d.%m.%Y %H:%M:%S')))