Total Complexity | 93 |
Total Lines | 440 |
Duplicated Lines | 0 % |
Complex classes like Orange.widgets.visualize.OWSieveDiagram often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | from collections import defaultdict |
||
24 | class OWSieveDiagram(OWWidget): |
||
25 | name = "Sieve Diagram" |
||
26 | description = "A two-way contingency table providing information in " \ |
||
27 | "relation to expected frequency of combination of feature " \ |
||
28 | "values under independence." |
||
29 | icon = "icons/SieveDiagram.svg" |
||
30 | priority = 4200 |
||
31 | |||
32 | inputs = [("Data", Table, "setData", Default), |
||
33 | ("Features", AttributeList, "setShownAttributes")] |
||
34 | outputs = [] |
||
35 | |||
36 | settingsList = ["showLines", "showCases", "showInColor"] |
||
37 | |||
38 | want_graph = True |
||
39 | |||
40 | def __init__(self,parent=None, signalManager = None): |
||
41 | OWWidget.__init__(self, parent, signalManager, "Sieve diagram", True) |
||
42 | |||
43 | #self.controlArea.setMinimumWidth(250) |
||
44 | |||
45 | #set default settings |
||
46 | self.data = None |
||
47 | |||
48 | self.attrX = "" |
||
49 | self.attrY = "" |
||
50 | self.attrCondition = "" |
||
51 | self.attrConditionValue = "" |
||
52 | self.showLines = 1 |
||
53 | self.showCases = 0 |
||
54 | self.showInColor = 1 |
||
55 | self.attributeSelectionList = None |
||
56 | self.stopCalculating = 0 |
||
57 | |||
58 | self.canvas = QGraphicsScene() |
||
59 | self.canvasView = QGraphicsView(self.canvas, self.mainArea) |
||
60 | self.mainArea.layout().addWidget(self.canvasView) |
||
61 | self.canvasView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) |
||
62 | self.canvasView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) |
||
63 | |||
64 | #GUI |
||
65 | self.attrSelGroup = gui.widgetBox(self.controlArea, box = "Shown attributes") |
||
66 | |||
67 | self.attrXCombo = gui.comboBox( |
||
68 | self.attrSelGroup, self, value="attrX", label="X attribute:", |
||
69 | orientation="horizontal", tooltip="Select an attribute to be shown on the X axis", |
||
70 | callback=self.updateGraph, sendSelectedValue=1, valueType=str, |
||
71 | labelWidth=70, contentsLength=12) |
||
72 | |||
73 | self.attrYCombo = gui.comboBox( |
||
74 | self.attrSelGroup, self, value="attrY", label="Y attribute:", |
||
75 | orientation="horizontal", tooltip="Select an attribute to be shown on the Y axis", |
||
76 | callback=self.updateGraph, sendSelectedValue=1, valueType=str, |
||
77 | labelWidth=70, contentsLength=12) |
||
78 | |||
79 | gui.separator(self.controlArea) |
||
80 | |||
81 | self.conditionGroup = gui.widgetBox(self.controlArea, box = "Condition") |
||
82 | self.attrConditionCombo = gui.comboBox( |
||
83 | self.conditionGroup, self, value="attrCondition", |
||
84 | label="Attribute:", orientation="horizontal", |
||
85 | callback=self.updateConditionAttr, sendSelectedValue=True, |
||
86 | valueType=str, labelWidth=70, contentsLength=12) |
||
87 | self.attrConditionValueCombo = gui.comboBox( |
||
88 | self.conditionGroup, self, value="attrConditionValue", |
||
89 | label="Value:", orientation="horizontal", callback=self.updateGraph, |
||
90 | sendSelectedValue=True, valueType=str, labelWidth=70, |
||
91 | contentsLength=10) |
||
92 | |||
93 | gui.separator(self.controlArea) |
||
94 | |||
95 | box2 = gui.widgetBox(self.controlArea, box = "Visual settings") |
||
96 | gui.checkBox(box2, self, "showLines", "Show squares (observed frequency)", callback = self.updateGraph) |
||
97 | hbox = gui.widgetBox(box2, orientation = "horizontal") |
||
98 | gui.checkBox(hbox, self, "showCases", "Show data instances...", callback = self.updateGraph) |
||
99 | gui.checkBox(hbox, self, "showInColor", "...in color", callback = self.updateGraph) |
||
100 | |||
101 | gui.separator(self.controlArea) |
||
102 | # self.optimizationDlg = OWSieveOptimization(self, self.signalManager) |
||
103 | # optimizationButtons = gui.widgetBox(self.controlArea, "Dialogs", orientation = "horizontal") |
||
104 | # gui.button(optimizationButtons, self, "VizRank", callback = self.optimizationDlg.reshow, debuggingEnabled = 0, tooltip = "Find attribute groups with highest value dependency") |
||
105 | |||
106 | gui.rubber(self.controlArea) |
||
107 | |||
108 | # self.wdChildDialogs = [self.optimizationDlg] # used when running widget debugging |
||
109 | # self.graphButton.clicked.connect(self.saveToFileCanvas) |
||
110 | self.icons = gui.attributeIconDict |
||
111 | self.resize(800, 550) |
||
112 | random.seed() |
||
113 | self.graphButton.clicked.connect(self.save_graph) |
||
114 | |||
115 | def sendReport(self): |
||
116 | self.startReport("%s [%s, %s]" % (self.windowTitle(), self.attrX, self.attrY)) |
||
117 | self.reportSettings("", |
||
118 | [("X-Attribute", self.attrX), ("Y-Attribute", self.attrY), |
||
119 | self.attrCondition != "(None)" and ("Condition", "%s = '%s'" % (self.attrCondition, self.attrConditionValue))]) |
||
120 | # self.reportImage(lambda *x: OWChooseImageSizeDlg(self.canvas).saveImage(*x)) |
||
121 | |||
122 | |||
123 | # receive new data and update all fields |
||
124 | def setData(self, data): |
||
125 | if type(data) == SqlTable and data.approx_len() > LARGE_TABLE: |
||
126 | data = data.sample_time(DEFAULT_SAMPLE_TIME) |
||
127 | |||
128 | self.information(0) |
||
129 | self.information(1) |
||
130 | sameDomain = self.data and data and self.data.domain.checksum() == data.domain.checksum() # preserve attribute choice if the domain is the same |
||
131 | # self.data = self.optimizationDlg.setData(data, 0) |
||
132 | self.data = data |
||
133 | |||
134 | if not sameDomain: |
||
135 | self.initCombos() |
||
136 | |||
137 | self.warning(0, "") |
||
138 | if data: |
||
139 | if any(attr.is_continuous for attr in data.domain): |
||
140 | self.warning(0, "Data contains continuous variables. " + |
||
141 | "Discretize the data to use them.") |
||
142 | |||
143 | self.setShownAttributes(self.attributeSelectionList) |
||
144 | |||
145 | ## Attribute selection signal |
||
146 | def setShownAttributes(self, attrList): |
||
147 | self.attributeSelectionList = attrList |
||
148 | if self.data and self.attributeSelectionList and len(attrList) >= 2: |
||
149 | attrs = [attr.name for attr in self.data.domain] |
||
150 | if attrList[0] in attrs and attrList[1] in attrs: |
||
151 | self.attrX = attrList[0] |
||
152 | self.attrY = attrList[1] |
||
153 | self.updateGraph() |
||
154 | |||
155 | |||
156 | |||
157 | # create data subset depending on conditional attribute and value |
||
158 | def getConditionalData(self, xAttr = None, yAttr = None, dropMissingData = 1): |
||
159 | if not self.data: return None |
||
160 | |||
161 | if not xAttr: xAttr = self.attrX |
||
162 | if not yAttr: yAttr = self.attrY |
||
163 | if not (xAttr and yAttr): return |
||
164 | |||
165 | if self.attrCondition == "(None)": |
||
166 | data = self.data[:, [xAttr, yAttr]] |
||
167 | # data = self.data.select([xAttr, yAttr]) |
||
168 | else: |
||
169 | # data = orange.Preprocessor_dropMissing(self.data.select([xAttr, yAttr, self.attrCondition])) |
||
170 | # data = self.data.select({self.attrCondition:self.attrConditionValue}) |
||
171 | fd = Orange.data.filter.FilterDiscrete(column=self.attrCondition, values=[self.attrConditionValue]) |
||
172 | filt = Orange.data.filter.Values([fd]) |
||
173 | filt.domain = self.data.domain |
||
174 | data = filt(self.data) |
||
175 | |||
176 | # if dropMissingData: return orange.Preprocessor_dropMissing(data) |
||
177 | #else: |
||
178 | return data |
||
179 | |||
180 | # new conditional attribute was set - update graph |
||
181 | def updateConditionAttr(self): |
||
182 | self.attrConditionValueCombo.clear() |
||
183 | |||
184 | if self.attrCondition != "(None)": |
||
185 | for val in self.data.domain[self.attrCondition].values: |
||
186 | self.attrConditionValueCombo.addItem(val) |
||
187 | self.attrConditionValue = str(self.attrConditionValueCombo.itemText(0)) |
||
188 | self.updateGraph() |
||
189 | |||
190 | # initialize lists for shown and hidden attributes |
||
191 | def initCombos(self): |
||
192 | self.attrXCombo.clear() |
||
193 | self.attrYCombo.clear() |
||
194 | self.attrConditionCombo.clear() |
||
195 | self.attrConditionCombo.addItem("(None)") |
||
196 | self.attrConditionValueCombo.clear() |
||
197 | |||
198 | if not self.data: return |
||
199 | for i, var in enumerate(self.data.domain): |
||
200 | if var.is_discrete: |
||
201 | self.attrXCombo.addItem(self.icons[self.data.domain[i]], self.data.domain[i].name) |
||
202 | self.attrYCombo.addItem(self.icons[self.data.domain[i]], self.data.domain[i].name) |
||
203 | self.attrConditionCombo.addItem(self.icons[self.data.domain[i]], self.data.domain[i].name) |
||
204 | self.attrCondition = str(self.attrConditionCombo.itemText(0)) |
||
205 | |||
206 | if self.attrXCombo.count() > 0: |
||
207 | self.attrX = str(self.attrXCombo.itemText(0)) |
||
208 | self.attrY = str(self.attrYCombo.itemText(self.attrYCombo.count() > 1)) |
||
209 | else: |
||
210 | self.attrX = None |
||
211 | self.attrY = None |
||
212 | |||
213 | def resizeEvent(self, e): |
||
214 | OWWidget.resizeEvent(self,e) |
||
215 | self.updateGraph() |
||
216 | |||
217 | def showEvent(self, ev): |
||
218 | OWWidget.showEvent(self, ev) |
||
219 | self.updateGraph() |
||
220 | |||
221 | ## updateGraph - gets called every time the graph has to be updated |
||
222 | def updateGraph(self, *args): |
||
223 | for item in self.canvas.items(): |
||
224 | self.canvas.removeItem(item) # remove all canvas items |
||
225 | if not self.data: return |
||
226 | if not self.attrX or not self.attrY: return |
||
227 | |||
228 | data = self.getConditionalData() |
||
229 | if not data or len(data) == 0: return |
||
230 | |||
231 | valsX = [] |
||
232 | valsY = [] |
||
233 | # contX = orange.ContingencyAttrAttr(self.attrX, self.attrX, data) # distribution of X attribute |
||
234 | # contY = orange.ContingencyAttrAttr(self.attrY, self.attrY, data) # distribution of Y attribute |
||
235 | contX = get_contingency(data, self.attrX, self.attrX) |
||
236 | contY = get_contingency(data, self.attrY, self.attrY) |
||
237 | |||
238 | # compute contingency of x and y attributes |
||
239 | for entry in contX: |
||
240 | sum_ = 0 |
||
241 | try: |
||
242 | for val in entry: sum_ += val |
||
243 | except: pass |
||
244 | valsX.append(sum_) |
||
245 | |||
246 | for entry in contY: |
||
247 | sum_ = 0 |
||
248 | try: |
||
249 | for val in entry: sum_ += val |
||
250 | except: pass |
||
251 | valsY.append(sum_) |
||
252 | |||
253 | # create cartesian product of selected attributes and compute contingency |
||
254 | # (cart, profit) = FeatureByCartesianProduct(data, [data.domain[self.attrX], data.domain[self.attrY]]) |
||
255 | # tempData = data.select(list(data.domain) + [cart]) |
||
256 | # contXY = orange.ContingencyAttrAttr(cart, cart, tempData) # distribution of X attribute |
||
257 | # contXY = get_contingency(tempData, cart, cart) |
||
258 | contXY = self.getConditionalDistributions(data, [data.domain[self.attrX], data.domain[self.attrY]]) |
||
259 | |||
260 | # compute probabilities |
||
261 | probs = {} |
||
262 | for i in range(len(valsX)): |
||
263 | valx = valsX[i] |
||
264 | for j in range(len(valsY)): |
||
265 | valy = valsY[j] |
||
266 | |||
267 | actualProb = 0 |
||
268 | try: |
||
269 | actualProb = contXY['%s-%s' %(data.domain[self.attrX].values[i], data.domain[self.attrY].values[j])] |
||
270 | # for val in contXY['%s-%s' %(i, j)]: actualProb += val |
||
271 | except: |
||
272 | actualProb = 0 |
||
273 | 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)) |
||
274 | |||
275 | #get text width of Y labels |
||
276 | max_ylabel_w = 0 |
||
277 | for j in range(len(valsY)): |
||
278 | xl = OWCanvasText(self.canvas, "", 0, 0, htmlText = getHtmlCompatibleString(data.domain[self.attrY].values[j]), show=False) |
||
279 | max_ylabel_w = max(int(xl.boundingRect().width()), max_ylabel_w) |
||
280 | max_ylabel_w = min(max_ylabel_w, 200) #upper limit for label widths |
||
281 | |||
282 | # get text width of Y attribute name |
||
283 | text = OWCanvasText(self.canvas, data.domain[self.attrY].name, x = 0, y = 0, bold = 1, show = 0, vertical=True) |
||
284 | xOff = int(text.boundingRect().height() + max_ylabel_w) |
||
285 | yOff = 55 |
||
286 | sqareSize = min(self.canvasView.width() - xOff - 35, self.canvasView.height() - yOff - 50) |
||
287 | if sqareSize < 0: return # canvas is too small to draw rectangles |
||
288 | self.canvasView.setSceneRect(0, 0, self.canvasView.width(), self.canvasView.height()) |
||
289 | |||
290 | # print graph name |
||
291 | if self.attrCondition == "(None)": |
||
292 | name = "<b>P(%s, %s) ≠ P(%s)×P(%s)</b>" %(self.attrX, self.attrY, self.attrX, self.attrY) |
||
293 | else: |
||
294 | 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)) |
||
295 | OWCanvasText(self.canvas, "" , xOff+ sqareSize/2, 20, Qt.AlignCenter, htmlText = name) |
||
296 | OWCanvasText(self.canvas, "N = " + str(len(data)), xOff+ sqareSize/2, 38, Qt.AlignCenter, bold = 0) |
||
297 | |||
298 | ###################### |
||
299 | # compute chi-square |
||
300 | chisquare = 0.0 |
||
301 | for i in range(len(valsX)): |
||
302 | for j in range(len(valsY)): |
||
303 | ((xAttr, xVal), (yAttr, yVal), actual, sum_) = probs['%s-%s' %(data.domain[self.attrX].values[i], data.domain[self.attrY].values[j])] |
||
304 | expected = float(xVal*yVal)/float(sum_) |
||
305 | if expected == 0: continue |
||
306 | pearson2 = (actual - expected)*(actual - expected) / expected |
||
307 | chisquare += pearson2 |
||
308 | |||
309 | ###################### |
||
310 | # draw rectangles |
||
311 | currX = xOff |
||
312 | max_xlabel_h = 0 |
||
313 | |||
314 | normX, normY = sum(valsX), sum(valsY) |
||
315 | for i in range(len(valsX)): |
||
316 | if valsX[i] == 0: continue |
||
317 | currY = yOff |
||
318 | width = int(float(sqareSize * valsX[i])/float(normX)) |
||
319 | |||
320 | #for j in range(len(valsY)): |
||
321 | for j in range(len(valsY)-1, -1, -1): # this way we sort y values correctly |
||
322 | ((xAttr, xVal), (yAttr, yVal), actual, sum_) = probs['%s-%s' %(data.domain[self.attrX].values[i], data.domain[self.attrY].values[j])] |
||
323 | if valsY[j] == 0: continue |
||
324 | height = int(float(sqareSize * valsY[j])/float(normY)) |
||
325 | |||
326 | # create rectangle |
||
327 | rect = OWCanvasRectangle(self.canvas, currX+2, currY+2, width-4, height-4, z = -10) |
||
328 | self.addRectIndependencePearson(rect, currX+2, currY+2, width-4, height-4, (xAttr, xVal), (yAttr, yVal), actual, sum_) |
||
329 | |||
330 | expected = float(xVal*yVal)/float(sum_) |
||
331 | pearson = (actual - expected) / sqrt(expected) |
||
332 | tooltipText = """<b>X Attribute: %s</b><br>Value: <b>%s</b><br>Number of instances (p(x)): <b>%d (%.2f%%)</b><hr> |
||
333 | <b>Y Attribute: %s</b><br>Value: <b>%s</b><br>Number of instances (p(y)): <b>%d (%.2f%%)</b><hr> |
||
334 | <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> |
||
335 | <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 ) |
||
336 | rect.setToolTip(tooltipText) |
||
337 | |||
338 | currY += height |
||
339 | if currX == xOff: |
||
340 | OWCanvasText(self.canvas, "", xOff, currY - height/2, Qt.AlignRight | Qt.AlignVCenter, htmlText = getHtmlCompatibleString(data.domain[self.attrY].values[j])) |
||
341 | |||
342 | xl = OWCanvasText(self.canvas, "", currX + width/2, yOff + sqareSize, Qt.AlignHCenter | Qt.AlignTop, htmlText = getHtmlCompatibleString(data.domain[self.attrX].values[i])) |
||
343 | max_xlabel_h = max(int(xl.boundingRect().height()), max_xlabel_h) |
||
344 | |||
345 | currX += width |
||
346 | |||
347 | # show attribute names |
||
348 | OWCanvasText(self.canvas, self.attrY, 0, yOff + sqareSize/2, Qt.AlignLeft | Qt.AlignVCenter, bold = 1, vertical=True) |
||
349 | OWCanvasText(self.canvas, self.attrX, xOff + sqareSize/2, yOff + sqareSize + max_xlabel_h, Qt.AlignHCenter | Qt.AlignTop, bold = 1) |
||
350 | |||
351 | #self.canvas.update() |
||
352 | |||
353 | # create a dictionary with all possible pairs of "combination-of-attr-values" : count |
||
354 | def getConditionalDistributions(self, data, attrs): |
||
355 | cond_dist = defaultdict(int) |
||
356 | all_attrs = [data.domain[a] for a in attrs] |
||
357 | if data.domain.class_var is not None: |
||
358 | all_attrs.append(data.domain.class_var) |
||
359 | |||
360 | for i in range(1, len(all_attrs) + 1): |
||
361 | attr = all_attrs[:i] |
||
362 | if type(data) == SqlTable: |
||
363 | # make all possible pairs of attributes + class_var |
||
364 | attr = [a.to_sql() for a in attr] |
||
365 | fields = attr + ["COUNT(*)"] |
||
366 | query = data._sql_query(fields, group_by=attr) |
||
367 | with data._execute_sql_query(query) as cur: |
||
368 | res = cur.fetchall() |
||
369 | for r in res: |
||
370 | str_values =[a.repr_val(a.to_val(x)) for a, x in zip(all_attrs, r[:-1])] |
||
371 | str_values = [x if x != '?' else 'None' for x in str_values] |
||
372 | cond_dist['-'.join(str_values)] = r[-1] |
||
373 | else: |
||
374 | for indices in product(*(range(len(a.values)) for a in attr)): |
||
375 | vals = [] |
||
376 | conditions = [] |
||
377 | for k, ind in enumerate(indices): |
||
378 | vals.append(attr[k].values[ind]) |
||
379 | fd = Orange.data.filter.FilterDiscrete(column=attr[k], values=[attr[k].values[ind]]) |
||
380 | conditions.append(fd) |
||
381 | filt = Orange.data.filter.Values(conditions) |
||
382 | filtdata = filt(data) |
||
383 | cond_dist['-'.join(vals)] = len(filtdata) |
||
384 | return cond_dist |
||
385 | |||
386 | ###################################################################### |
||
387 | ## show deviations from attribute independence with standardized pearson residuals |
||
388 | def addRectIndependencePearson(self, rect, x, y, w, h, xAttr_xVal, yAttr_yVal, actual, sum): |
||
389 | xAttr, xVal = xAttr_xVal |
||
390 | yAttr, yVal = yAttr_yVal |
||
391 | expected = float(xVal*yVal)/float(sum) |
||
392 | pearson = (actual - expected) / sqrt(expected) |
||
393 | |||
394 | if pearson > 0: # if there are more examples that we would expect under the null hypothesis |
||
395 | intPearson = floor(pearson) |
||
396 | pen = QPen(QColor(0,0,255), 1); rect.setPen(pen) |
||
397 | b = 255 |
||
398 | r = g = 255 - intPearson*20 |
||
399 | r = g = max(r, 55) # |
||
400 | elif pearson < 0: |
||
401 | intPearson = ceil(pearson) |
||
402 | pen = QPen(QColor(255,0,0), 1) |
||
403 | rect.setPen(pen) |
||
404 | r = 255 |
||
405 | b = g = 255 + intPearson*20 |
||
406 | b = g = max(b, 55) |
||
407 | else: |
||
408 | pen = QPen(QColor(255,255,255), 1) |
||
409 | r = g = b = 255 # white |
||
410 | color = QColor(r,g,b) |
||
411 | brush = QBrush(color); rect.setBrush(brush) |
||
412 | |||
413 | if self.showCases and w > 6 and h > 6: |
||
414 | if self.showInColor: |
||
415 | if pearson > 0: c = QColor(0,0,255) |
||
416 | else: c = QColor(255, 0,0) |
||
417 | else: c = Qt.black |
||
418 | for i in range(int(actual)): |
||
419 | 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) |
||
420 | |||
421 | if pearson > 0: |
||
422 | pearson = min(pearson, 10) |
||
423 | kvoc = 1 - 0.08 * pearson # if pearson in [0..10] --> kvoc in [1..0.2] |
||
424 | else: |
||
425 | pearson = max(pearson, -10) |
||
426 | kvoc = 1 - 0.4*pearson |
||
427 | |||
428 | self.addLines(x,y,w,h, kvoc, pen) |
||
429 | |||
430 | |||
431 | ################################################## |
||
432 | # add lines |
||
433 | def addLines(self, x,y,w,h, diff, pen): |
||
434 | if not self.showLines: return |
||
435 | if w == 0 or h == 0: return |
||
436 | |||
437 | # create lines |
||
438 | dist = 20 # original distance between two lines in pixels |
||
439 | dist = dist * diff |
||
440 | temp = dist |
||
441 | while (temp < w): |
||
442 | OWCanvasLine(self.canvas, temp+x, y, temp+x, y+h, 1, pen.color()) |
||
443 | temp += dist |
||
444 | |||
445 | temp = dist |
||
446 | while (temp < h): |
||
447 | OWCanvasLine(self.canvas, x, y+temp, x+w, y+temp, 1, pen.color()) |
||
448 | temp += dist |
||
449 | |||
450 | def saveToFileCanvas(self): |
||
451 | sizeDlg = OWChooseImageSizeDlg(self.canvas, parent=self) |
||
452 | sizeDlg.exec_() |
||
453 | |||
454 | def closeEvent(self, ce): |
||
455 | # self.optimizationDlg.hide() |
||
456 | QDialog.closeEvent(self, ce) |
||
457 | |||
458 | def save_graph(self): |
||
459 | from Orange.widgets.data.owsave import OWSave |
||
460 | |||
461 | save_img = OWSave(parent=self, data=self.canvas, |
||
462 | file_formats=FileFormats.img_writers) |
||
463 | save_img.exec_() |
||
464 | |||
636 |
This can be caused by one of the following:
1. Missing Dependencies
This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.
2. Missing __init__.py files
This error could also result from missing
__init__.py
files in your module folders. Make sure that you place one file in each sub-folder.