Ticket #640: OWSOMVisualizerMk2.py

File OWSOMVisualizerMk2.py, 58.3 KB (added by ales, 18 months ago)

A semi working implementation

Line 
1"""
2<name>SOM Visualizer Mk2</name>
3<description>Visualizes a trained self organising maps.</description>
4<icon>icons/SOMVisualizer.png</icon>
5<contact>Ales Erjavec (ales.erjavec(@at@)fri.uni-lj.si)</contact> 
6<priority>5020</priority>
7"""
8import orange, orngSOM
9import math, numpy
10import OWGUI, OWColorPalette
11from OWWidget import *
12
13from OWDlgs import OWChooseImageSizeDlg
14
15DefColor=QColor(200, 200, 0)
16BaseColor=QColor(0, 0, 200)
17
18from orngMisc import ColorPalette
19from functools import partial
20
21class ColorPaletteBW(ColorPalette):
22    def __init__(self, *args, **kwargs):
23        ColorPalette.__init__(self, [(0, 0, 0,), (255, 255, 255)], *args, **kwargs)
24       
25class ColorPaletteHSV(ColorPalette):
26    def __init__(self, num_of_colors):
27        ColorPalette.__init__(self, OWColorPalette.defaultRGBColors)
28        self.num_of_colors = num_of_colors
29       
30    def get_rgb(self, value, gamma=None):
31        if isinstance(value, int):
32            return self.colors[value]
33        else:
34            raise ValueError("int expected for discrete color palette")
35       
36class ColorPaletteGR(ColorPalette):
37    def __init__(self, *args, **kwargs):
38        ColorPalette.__init__(self, [(0, 255, 0), (255, 0, 0)], *args, **kwargs)
39       
40class GraphicsSOMItem(QGraphicsPolygonItem):
41    startAngle=0
42    segment=6
43    def __init__(self, *args):
44        QGraphicsPolygonItem.__init__(self, *args)
45        self.node=None
46        self.labelText=""
47        self.textColor=Qt.black
48        self.defaultPen = QPen(Qt.black)
49        self.outlinePoints=None
50        self.histogramConstructor = None
51        self.histogramItem = None
52        self.setSize(10)
53        self.setZValue(0)
54
55    def areaPoints(self):
56        return self.outlinePoints
57   
58    def setSize(self, size):
59        self.outlinePoints = [QPointF(math.cos(2*math.pi/self.segments*i + self.startAngle)*size,
60                                      math.sin(2*math.pi/self.segments*i + self.startAngle)*size)
61                              for i in range(self.segments)]
62        self.setPolygon(QPolygonF(self.outlinePoints))
63
64    def setColor(self, color):
65        self.color = color
66        if color.value() < 100:
67            self.textColor = Qt.white
68        self.setBrush(QBrush(color))
69
70    def setNode(self, node):
71        self.node = node
72        self.hasNode = True
73        self.setFlags(QGraphicsItem.ItemIsSelectable if node else 0)
74        self.updateToolTips()
75
76    def advancement(self):
77        pass
78
79    def updateToolTips(self, showToolTips=True, includeCodebook=True):
80        if self.node and showToolTips:
81            node = self.node
82            text = "Items: %i" % len(self.node.examples)
83            if includeCodebook:
84                text += "<hr><b>Codebook vector:</b><br>" + "<br>".join(\
85                    [a.variable.name + ": " + str(a) for a in node.referenceExample \
86                     if a.variable != node.referenceExample.domain.classVar])
87           
88            if node.examples.domain.classVar and len(node.examples):
89                dist = orange.Distribution(node.examples.domain.classVar, node.examples)
90                if node.examples.domain.classVar.varType == orange.VarTypes.Continuous:
91                    text += "<hr>Avg " + node.examples.domain.classVar.name + ":" + ("%.3f" % dist.average())
92                else:
93                    colors = OWColorPalette.ColorPaletteHSV(len(node.examples.domain.classVar.values))
94                    text += "<hr>" + "<br>".join(["<span style=\"color:%s\">%s</span>" %(colors[i].name(), str(value) + ": " + str(dist[i])) \
95                                                 for i, value in enumerate(node.examples.domain.classVar.values)])
96            self.setToolTip(text)
97        else:
98            self.setToolTip("")
99       
100    def setComponentPlane(self, component):
101        colorSchema = self.colorSchema(component)
102        if component is not None:
103            val = self.node.referenceExample[component]
104            color = colorSchema(val)
105        else:
106            color = QColor(Qt.white)
107        self.setBrush(QBrush(color))
108       
109    @property
110    def colorSchema(self):
111        return self.parentItem().colorSchema
112   
113    @property
114    def histogramColorSchema(self):
115        return self.parentItem().histogramColorSchema
116   
117    def setHistogramConstructor(self, constructor):
118        if getattr(self, "histogramItem", None) is not None:
119            self.scene().removeItem(self.histogramItem)
120            self.histogramItem = None
121        self.histogramConstructor = constructor
122        self.updateHistogram()
123       
124    def updateHistogram(self):
125        if self.histogramItem is not None:
126            self.scene().removeItem(self.histogramItem)
127            self.histogramItem = None
128        if self.histogramConstructor and self.node and self.node.mappedExamples:
129            self.histogramItem = self.histogramConstructor(self.node.mappedExamples, self)
130            self.histogramItem.setParentItem(self)
131            # center the item
132            rect = self.histogramItem.boundingRect()
133           
134            self.histogramItem.setPos(-rect.center())
135           
136    def paint(self, painter, option, widget=0):
137        painter.setBrush(self.brush())
138        if self.isSelected():
139            painter.setPen(QPen(Qt.red, 2))
140        else:
141            painter.setPen(self.pen())
142        painter.drawPolygon(self.polygon())
143       
144    def itemChange(self, change, value):
145        if change == QGraphicsItem.ItemSelectedHasChanged:
146            self.setZValue(self.zValue() + (1 if value.toPyObject() else -1))
147        return QGraphicsPolygonItem.itemChange(self, change, value)
148                       
149class GraphicsSOMHexagon(GraphicsSOMItem):
150    startAngle = 0
151    segments = 6
152    def advancement(self):
153        width = self.outlinePoints[0].x() - self.outlinePoints[3].x()
154        line = self.outlinePoints[1].x() - self.outlinePoints[2].x()
155        x = width - (width-line)/2
156        y = self.outlinePoints[2].y() - self.outlinePoints[5].y()
157        return (x, y)
158
159class GraphicsSOMRectangle(GraphicsSOMItem):
160    startAngle = math.pi/4
161    segments = 4
162    def advancement(self):
163        x = self.outlinePoints[0].x() - self.outlinePoints[1].x()
164        y = self.outlinePoints[0].y() - self.outlinePoints[3].y()
165        return (x,y)
166   
167class PieChart(QGraphicsEllipseItem):
168    def __init__(self, attr, examples, *args, **kwargs):
169        QGraphicsEllipseItem.__init__(self, *args)
170        self.distribution = orange.Distribution(attr, examples)
171        self.setRect(0.0, 0.0, 20.0, 20.0)
172       
173    def paint(self, painter, option, widget=0):
174        start, stop = self.startAngle(), self.startAngle() + self.spanAngle()
175        span = stop - start
176        distsum = sum(self.distribution)
177        angle = start
178        dspan = span / distsum
179        colorSchema = self.colorSchema
180        for i, count in enumerate(self.distribution): 
181            color = colorSchema(i)
182            painter.setBrush(QBrush(color))
183            arcSpan = count * dspan
184            painter.drawPie(self.rect(), angle, arcSpan)
185            angle = angle + arcSpan
186           
187    @property
188    def colorSchema(self):
189        try:
190            self.no_attr
191            return self.parentItem().histogramColorSchema
192        except AttributeError:
193            return lambda val: OWColorPalette.ColorPaletteHSV(len(self.distribution))[val]
194       
195    @classmethod
196    def legendItemConstructor(cls, attr, examples, *args, **kwargs):
197        return DiscreteLegendItem(attr, examples, *args, **kwargs)
198       
199class MajorityChart(PieChart):
200    def __init__(self, *args, **kwargs):
201        PieChart.__init__(self, *args, **kwargs)
202        self.majorityValue = self.distribution.modus()
203        index = int(numpy.argmax(list(self.distribution)))
204        self.setBrush(QBrush(self.colorSchema(index)))
205       
206    def paint(self, painter, option, widget=0):
207        QGraphicsEllipseItem.paint(self, painter, option, widget)
208       
209class MajorityProbChart(MajorityChart):
210    def __init__(self, *args, **kwargs):
211        MajorityChart.__init__(self, *args, **kwargs)
212        index = int(numpy.argmax(list(self.distribution)))
213        val = self.distribution[self.majorityValue]
214        prob = float(val) / (sum(self.distribution) or 1)
215#        colorSchema = self.colorSchema(self.distribution.variable)
216        color = self.colorSchema(index)
217        color = color.lighter(200 - 100 * prob)
218        self.setBrush(QColor(color))
219       
220class ContChart(QGraphicsEllipseItem):
221    def __init__(self, attr, examples, *args, **kwargs):
222        QGraphicsEllipseItem.__init__(self, *args)
223        self.distribution = orange.Distribution(attr, examples)
224        self.setRect(0, 0, 20, 20)
225        self.setBrush(QBrush(DefColor))
226       
227    @property
228    def colorSchema(self):
229        try:
230            return self.parentItem().histogramColorSchema
231        except AttributeError, ex:
232            raise
233   
234    @classmethod
235    def legendItemConstructor(cls, attr, examples, *args, **kwargs):
236        return LegendItem(attr, examples, *args, **kwargs)
237       
238class AverageValue(ContChart):
239    def __init__(self, *args, **kwargs):
240        ContChart.__init__(self, *args, **kwargs)
241        try:
242            self.average = self.distribution.average()
243        except orange.KernelException:
244            self.average = None
245        colorSchema = self.colorSchema(self.distribution.variable)
246        color = lambda col: col if isinstance(col, QColor) else QColor(*col)
247        color = color(colorSchema(self.average)) if self.average is not None else Qt.white
248        self.setBrush(QBrush(color))
249       
250    @classmethod
251    def legendItemConstructor(cls, attr, examples, *args, **kwargs):
252        item = LegendItem(attr, examples, *args, **kwargs)
253        if examples:
254            dist = orange.Distribution(attr, examples)
255            minval, maxval = min(dist.keys()), max(dist.keys())
256            item.setScale((minval, maxval))
257        return item
258   
259class SOMMapItem(QAbstractGraphicsShapeItem):
260    """ A SOM graphics object
261    """
262    objSize = 15
263    def __init__(self, SOMMap=None, w=None, h=None, parent=None, scene=None):
264        QAbstractGraphicsShapeItem.__init__(self, parent, None)
265        self.histogramData = None
266        if map is not None:
267            self.setSOMMap(SOMMap)
268           
269        if scene is not None:
270            scene.addItem(self)
271           
272    def setSOMMap(self, map):
273        if map is not None:
274            if map.topology == orngSOM.HexagonalTopology:
275                self._setHexagonalMap(map)
276            elif map.topology == orngSOM.RectangularTopology:
277                self._setRectangularMap(map)
278        else:
279            self.map = None
280        self.prepareGeometryChange()
281       
282    def _setHexagonalMap(self, map):
283        self.map = map
284        xdim, ydim = map.map_shape
285        size = 2*self.objSize - 1
286        x, y = size, size*2
287        for n in self.map.map:
288            offset = 1 - abs(int(n.pos[0])%2 - 2)
289            h=GraphicsSOMHexagon(self)
290            h.setSize(size)
291            h.setNode(n)
292            (xa, ya) = h.advancement()
293            h.setPos(x + n.pos[0]*xa, y + n.pos[1]*ya + offset*ya/2)
294            h.show()
295       
296    def _setRectangularMap(self, map):
297        self.map = map
298        size=self.objSize*2-1
299        x,y=size, size
300        for n in self.map.map:
301            r=GraphicsSOMRectangle(self)
302            r.setSize(size)
303            r.setNode(n)
304            (xa,ya) = r.advancement()
305            r.setPos(x + n.pos[0]*xa, y + n.pos[1]*ya)
306            r.show()
307           
308    def boundingRect(self):
309        return self.childrenBoundingRect()
310       
311    def paint(self, painter, options, widget=0):
312        pass
313   
314    def setComponentPlane(self, component):
315        for node in self.nodes():
316            node.setComponentPlane(component)
317           
318    def setHistogram(self, attr):
319        for node in self.nodes():
320            node.setHistogram(attr)
321       
322    def nodes(self):
323        for item in self.childItems():
324            if isinstance(item, GraphicsSOMItem):
325                yield item
326               
327    def setColorSchema(self, schema):
328        self._colorSchema = schema
329       
330    @property
331    def colorSchema(self):
332        """ Color schema for component planes
333        """
334        if hasattr(self, "_colorSchema"):
335            return self._colorSchema
336        else:
337            def colorSchema(attr):
338                if attr is None:
339                    return lambda val: QColor(Qt.white)
340                elif type(attr) == int:
341                    attr = self.map.examples.domain[attr]
342                if attr.varType == orange.VarTypes.Discrete:
343                    index = self.map.examples.domain.index(attr)
344                    vals = [n.vector[index] for n in self.map.map]
345                    minval, maxval = min(vals), max(vals)
346                    return lambda val: OWColorPalette.ColorPaletteBW()[min(max(1 - (val - minval) / (maxval - minval or 1), 0.0), 1.0)]
347                else:
348                    index = self.map.examples.domain.index(attr)
349                    vals = [n.vector[index] for n in self.map.map]
350                    minval, maxval = min(vals), max(vals)
351                    return lambda val: OWColorPalette.ColorPaletteBW()[ 1 - (val - minval) / (maxval - minval or 1)] 
352            return colorSchema
353       
354    def setHistogramColorSchema(self, schema):
355        self._histogramColorSchema = schema
356       
357    @property
358    def histogramColorSchema(self):
359        """ Color schema for histograms
360        """
361        if hasattr(self, "_histogramColorSchema"):
362            def colorSchema(attr):
363                if attr is None:
364                    return lambda val: QColor(Qt.white)
365                elif type(attr) == int:
366                    attr = self.map.examples.domain[attr]
367                   
368                if attr.varType == orange.VarTypes.Discrete:
369                    return schema
370                else:
371                    index = self.map.examples.domain.index(attr)
372                    arr, c, w = self.histogramData.toNumpyMA()
373                    if index == arr.shape[1]:
374                        vals = c
375                    else:
376                        vals = arr[:,index]
377                    minval, maxval = numpy.min(vals), numpy.max(vals)
378                    def f(val):
379                        return self._histogramColorSchema((val - minval) / (maxval - minval or 1))
380                    return f
381            return colorSchema
382        else:
383            def colorSchema(attr):
384                if attr is None:
385                    return lambda val: QColor(Qt.white)
386                elif type(attr) == int:
387                    attr = self.map.examples.domain[attr]
388               
389                if attr.varType == orange.VarTypes.Discrete:
390                    return lambda val: OWColorPalette.ColorPaletteHSV(self.map.examples.domain[attr].values)[val]
391                else:
392                    index = self.map.examples.domain.index(attr)
393                    vals = [n.vector[index] for n in self.map.map]
394                    minval, maxval = min(vals), max(vals)
395                    return lambda val: OWColorPalette.ColorPaletteBW()[1 - (val - minval) / (maxval - minval or 1)] 
396            return colorSchema
397       
398    def componentRange(self, attr = None):
399        vectors = self.map.map.vectors()
400        range = zip(numpy.min(vectors, axis=0), numpy.max(vectors, axis=0))
401        if attr is not None:
402            return range[attr]
403        else:
404            return range
405       
406    def setNodePen(self, pen):
407        for node in self.nodes():
408            node.setPen(pen)
409           
410    def setHistogramData(self, data):
411        self.histogramData = data
412        for node in self.map:
413            node.mappedExamples = orange.ExampleTable(data.domain)
414        for example in data:
415            bmn = self.map.getBestMatchingNode(example)
416            bmn.mappedExamples.append(example)
417           
418        self.updateHistogram()
419
420    def setHistogramConstructor(self, constructor):
421        for item in self.nodes():
422            item.setHistogramConstructor(constructor)
423        self.updateHistogramSize()
424           
425    def updateHistogram(self):
426        for item in self.nodes():
427            item.updateHistogram()
428        self.updateHistogramSize()
429       
430    def updateHistogramSize(self):
431        if not self.histogramData:
432            return
433        maxmapped = max([len(getattr(node.node, "mappedExamples", [])) for node in self.nodes()])
434        for node in self.nodes():
435            if node.node is not None and node.node.mappedExamples and node.histogramItem is not None:
436                mapped = len(node.node.mappedExamples)
437                size = node.boundingRect().width() * 0.75
438                size = size  * mapped / maxmapped
439                rect = QRectF(0.0, 0.0, size, size)
440                rect.moveCenter(node.histogramItem.rect().center())
441                node.histogramItem.setRect(rect)
442               
443    def updateToolTips(self, *args, **kwargs):
444        for node in self.nodes():
445            node.updateToolTips(*args, **kwargs)
446         
447class SOMUMatrixItem(SOMMapItem):
448    """ A SOM U-Matrix graphics object
449    """
450    def setSOMMap(self, map):
451        self.map = map
452        self.somNodeMap = dict([(tuple(n.pos), n) for n in self.map])
453        if self.map.topology==orngSOM.HexagonalTopology:
454            self._setHexagonalUMat(map)
455        elif self.map.topology==orngSOM.RectangularTopology:
456            self._setRectangularUMat(map)
457        self.prepareGeometryChange()
458       
459    def _setHexagonalUMat(self, map):
460        self.map = map 
461        self.uMat = orngSOM.getUMat(map)
462        size=2*int(self.map.map_shape[0]*self.objSize/(2*self.map.map_shape[0]-1))-1
463        x,y=size, size
464        maxDist=max(reduce(numpy.maximum, [a for a in self.uMat]))
465        minDist=max(reduce(numpy.minimum, [a for a in self.uMat]))
466        for i in range(len(self.uMat)):
467            offset = 2 - abs(i % 4 - 2)
468            for j in range(len(self.uMat[i])):
469                h=GraphicsSOMHexagon(self)
470                h.setSize(size)
471                (xa,ya)=h.advancement()
472                h.setPos(x+i*xa, y+j*ya+offset*ya/2)
473                if i%2==0 and j%2==0:
474                    h.setNode(self.somNodeMap[(i/2,j/2)])
475                h.show()
476                val=255-min(max(255*(self.uMat[i][j]-minDist)/(maxDist-minDist),10),245)
477                h.setColor(QColor(val, val, val))
478               
479    def _setRectangularUMat(self, map):
480        self.map = map
481        self.uMat = orngSOM.getUMat(map)
482        size=2*int(self.map.map_shape[0]*self.objSize/(2*self.map.map_shape[0]-1))-1
483        x,y=size, size
484       
485        maxDist=max(reduce(numpy.maximum, [a for a in self.uMat]))
486        minDist=max(reduce(numpy.minimum, [a for a in self.uMat]))
487        for i in range(len(self.uMat)):
488            for j in range(len(self.uMat[i])):
489                r=GraphicsSOMRectangle(self)
490                r.setSize(size)
491                if i%2==0 and j%2==0:
492                    r.setNode(self.somNodeMap[(i/2,j/2)])
493                (xa,ya)=r.advancement()
494                r.setPos(x+i*xa, y+j*ya)
495                r.show()
496                val=255-min(max(255*(self.uMat[i][j]-minDist)/(maxDist-minDist),10),245)
497                r.setColor(QColor(val, val, val))
498
499class LayoutItemWrapper(QGraphicsWidget):
500    def __init__(self, item, parent=None):
501        self.item = item
502        QGraphicsWidget.__init__(self, parent)
503        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
504       
505    def setGeometry(self, rect):
506        self.item.setPos(rect.topLeft())
507        return QGraphicsWidget.setGeometry(self, rect)
508   
509    def sizeHint(self, which, constraint):
510        return self.item.boundingRect().size()
511       
512class AxisItem(QGraphicsWidget):
513    orientation = Qt.Horizontal
514    tickAlign = Qt.AlignBottom
515    textAlign = Qt.AlignHCenter | Qt.AlignBottom
516    axisScale = (0.0, 1.0)
517    def __init__(self, parent=None):
518        QGraphicsWidget.__init__(self, parent) 
519        self.tickCount = 5
520       
521    def setOrientation(self, orientation):
522        self.prepareGeometryChange()
523        self.orientation = orientation
524        if self.orientation == Qt.Horizontal:
525            self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
526        else:
527            self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)
528        self.updateGeometry()
529       
530    def ticks(self):
531        minval, maxval = self.axisScale
532        ticks = ["%.2f" % val for val in numpy.linspace(minval, maxval, self.tickCount)]
533        return ticks
534   
535    def paint(self, painter, option, widget=0):
536        painter.setFont(self.font())
537        size = self.geometry().size()
538        metrics = QFontMetrics(painter.font())
539        minval, maxval = self.axisScale
540        tickCount = 5
541       
542        if self.orientation == Qt.Horizontal:
543            spanx, spany = 0.0, size.width()
544            xadv, yadv =  spanx/ tickCount, 0.0
545            tick_w, tick_h = 0.0, 5.0
546            tickOffset = QPointF(0.0, 0.0)
547        else:
548            spanx, spany = 0.0, size.height()
549            xadv, yadv = 0.0, spany / tickCount
550            tick_w, tick_h = 5.0, 0.0
551            tickFunc = lambda : (y / spany)
552            tickOffset = QPointF(tick_w + 1.0, metrics.ascent()/2)
553           
554        ticks = self.ticks()
555           
556        xstart, ystart = 0.0, 0.0
557        painter.drawLine(xstart, ystart, xstart + tickCount*xadv, ystart + tickCount*yadv)
558       
559        linspacex = numpy.linspace(0.0, spanx, tickCount)
560        linspacey = numpy.linspace(0.0, spany, tickCount)
561       
562        for x, y, tick in zip(linspacex, linspacey, ticks):
563            painter.drawLine(x, y, x + tick_w, y + tick_h)
564            painter.drawText(QPointF(x, y) + tickOffset, tick)
565       
566    def setGeometry(self, rect):
567        self.prepareGeometryChange()
568        return QGraphicsWidget.setGeometry(self, rect)
569       
570    def sizeHint(self, which, *args):
571        minval, maxval = self.axisScale
572        ticks = self.ticks()
573        metrics = QFontMetrics(self.font())
574        if self.orientation == Qt.Horizontal:
575            h = metrics.height() + 5
576            w = 100 
577        else:
578            h = 100
579            w = max([metrics.width(t) for t in ticks]) + 5
580        return QSizeF(w, h)
581   
582    def boundingRect(self):
583        metrics = QFontMetrics(self.font())
584        return QRectF(QPointF(0.0, 0.0), self.sizeHint(None)).adjusted(0, -metrics.ascent(), 5, metrics.ascent())   
585   
586    def setAxisScale(self, min, max):
587        self.axisScale = (min, max)
588        self.updateGeometry()
589       
590    def setAxisTicks(self, ticks):
591        if isinstance(ticks, dict):
592            self.ticks = ticks
593        self.updateGeometry()
594           
595    def tickLayout(self):
596        min, max = getattr(self, "axisScale", (0.0, 1.0))
597        ticks = self.ticks
598        span = max - min
599        span_log = math.log10(span)
600        log_sign = -1 if log_sign < 0.0 else 1
601        span_log = math.floor(span_log)
602        majorTicks = [(x, 5.0, tick(i, span_log)) for i in range(5)]
603        minorTicks = [(x, 3.0, tick(i, span_log + log_sign))  for i in range(10)]
604        return [(i, major, label) for i, tick, label in majorTicks]
605       
606class LegendRect(QGraphicsWidget):
607    orientation = Qt.Horizontal
608    def __init__(self, parent=None):
609        QGraphicsWidget.__init__(self, parent)
610        self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum)
611        self.setColorSchema(lambda val: OWColorPalette.ColorPaletteBW()[1 - val])
612       
613    def setOrientation(self, orientation):
614        self.orientation = orientation
615        if orientation == Qt.Horizontal:
616            self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum)
617        else:
618            self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.MinimumExpanding)
619        self.setColorSchema(self.colorSchema)
620           
621
622    def paint(self, painter, option, widget=0):
623        size = self.geometry().size()
624        painter.setBrush(QBrush(self.gradient))
625        painter.drawRect(0.0, 0.0, size.width(), size.height())
626       
627    def setColorSchema(self, schema):
628        self.colorSchema = schema
629        self.gradient = QLinearGradient(0.0, 0.0, 0.0, 1.0)
630        space = numpy.linspace(0.0, 1.0, 10)
631        color = lambda col: col if isinstance(col, QColor) else QColor(*col)
632        self.gradient.setStops([(x, color(self.colorSchema(x))) for x in space])
633        if hasattr(self.gradient, "setCoordinateMode"):
634            self.gradient.setCoordinateMode(QGradient.ObjectBoundingMode)
635        else:
636            if self.orientation == Qt.Vertical:
637                self.gradient.setStart(self.geometry().topRight())
638                self.gradient.setFinalStop(self.geometry().bottomRight())
639            else:
640                self.gradient.setStart(self.geometry().topRight())
641                self.gradient.setFinalStop(self.geometry().topLeft())
642       
643    def sizeHint(self, which, *args):
644        if self.orientation == Qt.Horizontal:
645            return QSizeF(100, 20)
646        else:
647            return QSizeF(20, 100)
648   
649    def setGeometry(self, rect):
650        QGraphicsWidget.setGeometry(self, rect)
651        if getattr(self, "colorSchema", None) is not None:
652            self.setColorSchema(self.colorSchema) # reset the colorSchema for the new geometry
653       
654class LegendItem(QGraphicsWidget):
655    schema = ColorPalette([(255, 255, 255), (0, 0, 0)])
656    range = (0.0, 1.0)
657    orientation = Qt.Horizontal
658    textAlign = Qt.AlignVCenter | Qt.AlignTop
659    textFont = QFont()
660    def __init__(self, attr, examples=None, parent=None, **kwargs):
661        QGraphicsWidget.__init__(self, parent)
662        self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum)
663        self.setLayout(QGraphicsLinearLayout(Qt.Vertical))
664        self.layout().setSpacing(0)
665        self.rectItem = LegendRect(self)
666        self.rectItem.setOrientation(self.orientation)
667        self.layout().addItem(self.rectItem)
668        self.axisItem = AxisItem(self)
669        self.axisItem.setOrientation(self.orientation)
670        self.layout().addItem(self.axisItem)
671       
672    def setOrientation(self, orientation=Qt.Horizontal):
673        self.orientation = orientation
674        self.rectItem.setOrientation(orientation)
675        self.axisItem.setOrientation(orientation)
676        if orientation == Qt.Horizontal:
677            self.layout().setOrientation(Qt.Vertical)
678            self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum)
679        else:
680            self.layout().setOrientation(Qt.Horizontal)
681            self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.MinimumExpanding)
682        self.updateGeometry()
683       
684    def setTextAlign(self, align):
685        self.textAlign = align
686   
687    def setTextFont(self, font):
688        self.textFont = font
689       
690    def setColorSchema(self, schema):
691        self.schema = schema
692        self.rectItem.setColorSchema(schema)
693       
694    def setTicks(self, ticks):
695        self.ticks = ticks
696
697    def setScale(self, scale):
698        self.scale = scale
699        self.axisItem.setAxisScale(*scale)
700       
701    def setGeometry(self, rect):
702        QGraphicsWidget.setGeometry(self, rect)
703   
704class DiscreteLegendItem(QGraphicsWidget):
705    class _ItemPair(QGraphicsItemGroup):
706        def __init__(self, item1, item2, parent=None):
707            QGraphicsItemGroup.__init__(self, parent)
708            self.item1, self.item2 = item1, item2
709            self.addToGroup(item1)
710            self.addToGroup(item2)
711            self.item1.setPos(0, item1.boundingRect().height()/2)
712            self.item2.setPos(self.item1.boundingRect().width(), 0.0)
713           
714    orientation = Qt.Vertical
715    def __init__(self, attr, examples=None, colorSchema=None, parentItem=None, scene=None, itemShape=QGraphicsEllipseItem):
716        QGraphicsWidget.__init__(self, parentItem)
717        self.attr = attr
718        self.colorSchema = (lambda val: OWColorPalette.ColorPaletteHSV(len(attr.values))[val]) if colorSchema is None else colorSchema
719        self.itemShape = itemShape
720        self.setLayout(QGraphicsLinearLayout(self.orientation))
721       
722        self.legendItems = []
723        for i, value in enumerate(attr.values):
724            self.addLegendItem(attr, value, self.colorSchema(i))
725       
726    def addLegendItem(self, attr, value, color=None, size=10):
727        item = self.itemShape(self)
728        item.setBrush(QBrush(self.colorSchema(len(self.legendItems)) if color is None else color))
729        item.setRect(QRectF(QPointF(0, 0), QSizeF(size, size)))
730       
731        text = QGraphicsTextItem(value, self)
732        pair = self._ItemPair(item, text, self)
733#        pair.setToolTip(value)
734        self.layout().addItem(LayoutItemWrapper(pair))
735       
736    def setOrientation(self, orientation):
737        self.orientation = orientation
738        self.layout().setOrientation(orientation)
739       
740    def setGeometry(self, rect):
741        QGraphicsWidget.setGeometry(self, rect)
742
743
744class SOMGraphicsWidget(QGraphicsWidget):
745    """ Graphics widget containing a SOM map and optional legends
746    """
747    def __init__(self, parent=None):
748        QGraphicsWidget.__init__(self, parent)
749        self.setLayout(QGraphicsLinearLayout())
750        self.legendLayout = QGraphicsLinearLayout(Qt.Vertical)
751        self.layout().addItem(self.legendLayout)
752        self.componentLegendItem = None
753        self.histLegendItem = None
754       
755    def clear(self):
756        for i in range(self.layout().count()):
757            self.removeAt(i)
758           
759    def setSOM(self, som):
760        self.som = som
761        if getattr(self, "somItem", None) is not None:
762            self.layout().removeAt(0)
763            self.scene().removeItem(self.somItem)
764        self.somItem = SOMMapItem(som, parent=self)
765        self.layout().insertItem(0, LayoutItemWrapper(self.somItem))
766       
767    def setComponentPlane(self, attr):
768        self.componentPlane = attr
769        self.somItem.setComponentPlane(attr)
770        if self.componentLegendItem is not None:
771            self.scene().removeItem(self.componentLegendItem)
772            self.componentLegendItem = None
773        if attr is not None:
774            self.componentLegendItem = LegendItem(attr, parent=self)
775            self.componentLegendItem.setOrientation(Qt.Vertical)
776            self.legendLayout.insertItem(0, self.componentLegendItem)
777            self.componentLegendItem.setScale(self.somItem.componentRange(attr))
778       
779    def setHistogramData(self, data):
780        self.histogramData = data
781        self.somItem.setHistogramData(data)
782       
783    def setHistogramConstructor(self, constructor):
784        self.histogramConstructor = constructor
785        self.somItem.setHistogramConstructor(constructor)
786        if self.histLegendItem is not None:
787            self.scene().removeItem(self.histLegendItem)
788            self.histLegendItem = None
789        if constructor and getattr(constructor, "legendItemConstructor", None) is not None:
790            self.histLegendItem = constructor.legendItemConstructor(self.histogramData)
791            self.histLegendItem.setOrientation(Qt.Vertical)             
792           
793            self.legendLayout.insertItem(1, self.histLegendItem)
794           
795    def setHistogram(self, attr, type_):
796        def const(*args, **kwargs):
797            return type_(attr, self.data, *args, **kwargs)
798       
799        self.histogramConstructor = const
800        self.histogramConstructorType = type_
801        self.histogramAttr = attr
802        self.setHistogramConstructor(self.histogramConstructor)
803           
804    def setColorSchema(self, schema):
805        self.colorSchema = schema
806        self.update()
807       
808    def setHistogramColorSchema(self, schema):
809        self.histogramColorSchema = schema
810        self.somItem.setHistogramColorSchema(schema)
811        self.update()
812       
813    def paint(self, painter, option, widget=0):
814        painter.drawRect(self.boundingRect())
815       
816    def setColorPalette(self, palette):
817        self.colorPalete = palette
818        self.somItem.setColorPalette(palette)
819        self.updateLegendItems()
820       
821class SOMGraphicsUMatrix(SOMGraphicsWidget):
822    def setSOM(self, som):
823        self.som = som
824        if getattr(self, "somItem", None) is not None:
825            self.scene().removeItem(self.somItem)
826            self.somItem = None
827        self.somItem = SOMUMatrixItem(som, parent=self)
828        self.layout().insertItem(0, LayoutItemWrapper(self.somItem))
829       
830    def setComponentPlane(self, *args):
831        raise NotImplemented
832   
833baseColor = QColor(20,20,20)
834
835class SceneSelectionManager(QObject):
836    def __init__(self, scene):
837        QObject.__init__(self, scene)
838        self.scene = scene
839        self.selection = []
840       
841    def start(self, event):
842        pos = event.scenePos()
843        if event.modifiers() & Qt.ControlModifier:
844            self.selection.append((pos, pos + QPointF(1, 1)))
845        else:
846            self.selection = [(pos, pos)]
847        self.emit(SIGNAL("selectionGeometryChanged()"))
848       
849    def update(self, event):
850        pos = event.scenePos() + QPointF(2.0, 2.0)
851        self.selection[-1] = self.selection[-1][:-1] + (pos,)
852        self.emit(SIGNAL("selectionGeometryChanged()"))
853   
854    def end(self, event):
855        self.update(event)
856       
857    def testSelection(self, data):
858        data = numpy.asarray(data)
859        region = QPainterPath()
860        for p1, p2 in self.selection:
861            region.addRect(QRectF(p1, p2).normalized())
862        def test(point):
863            return region.contains(QPointF(point[0], point[1]))
864        test = numpy.apply_along_axis(test, 1, data)
865        return test
866   
867    def selectionArea(self):
868        region = QPainterPath()
869        for p1, p2 in self.selection:
870            region.addRect(QRectF(p1, p2).normalized())
871        return region
872   
873    def lastSelectionRect(self):
874        if self.selection:
875            return QRectF(*self.selection[-1]).normalized()
876        else:
877            return None
878           
879class SOMScene(QGraphicsScene):
880    def __init__(self, parent=None):
881        QGraphicsScene.__init__(self, parent)
882        self.histogramData = None
883        self.histogramConstructor = None
884        self.histogramColorSchema = None
885        self.componentColorSchema = None
886        self.componentPlane = None
887        self.somWidget = None
888       
889    def clear(self):
890        QGraphicsScene.clear(self)
891        self.histogramData = None
892        self.histogramConstructor = None
893        self.histogramColorSchema = None
894        self.componentColorSchema = None
895        self.componentPlane = None
896        self.somWidget = None
897       
898    def setSom(self, map):
899        self.clear()
900        self.map = map
901       
902        self.emit(SIGNAL("som_changed()"))
903       
904        self.selectionManager = SceneSelectionManager(self)
905        self.connect(self.selectionManager, SIGNAL("selectionGeometryChanged()"), self.onSelectionAreaChange)
906       
907    def setComponentPlane(self, attr):
908        if type(self.somWidget) != SOMGraphicsWidget:
909            self.clear()
910            self.somWidget = SOMGraphicsWidget(None)
911            self.somWidget.setSOM(self.map)
912            self.somWidget.setComponentPlane(attr)
913            self.addItem(self.somWidget)
914            self.componentPlane = None
915            self.histogramData = None
916            self.histogramConstructor = None
917           
918        if attr is not self.componentPlane:
919            self.componentPlane = attr
920            for item in self.somWidgets():
921                item.setComponentPlane(attr)
922               
923    def setComponentPlanes(self, components):
924        self.clear()
925        self.topWidget = QGraphicsWidget(None)
926        self.topLayout = QGraphicsGridLayout() 
927        self.topWidget.setLayout(self.topLayout)
928        for i, c in enumerate(components):
929            w = SOMGraphicsWidget(self.topWidget)
930            w.setSOM(self.map)
931            w.setComponentPlane(c)
932            self.topLayout.addItem(w, i / 3, i % 3)
933           
934        self.addItem(self.topWidget)
935        self.topWidget.show()
936               
937    def setUMatrix(self):
938#        if type(self.somWidget) != SOMGraphicsUMatrix:
939        self.clear()
940        self.somWidget = SOMGraphicsUMatrix(None)
941        self.somWidget.setSOM(self.map)
942        self.addItem(self.somWidget)
943       
944    def somWidgets(self):
945        for item in self.items():
946            if isinstance(item, SOMGraphicsWidget):
947                yield item
948       
949    def setHistogramData(self, data):
950        if data is not self.histogramData:
951            self.histogramData = data
952            for item in self.somWidgets():
953                item.setHistogramData(data)
954               
955    def setHistogramConstructor(self, constructor):
956        if self.histogramConstructor is not constructor:
957            self.histogramConstructor = constructor
958            for item in self.somWidgets():
959                item.setHistogramConstructor(constructor)
960           
961    def setHistogramColorSchema(self, schema):
962        if schema is not self.histogramColorSchema:
963            self.histogramColorSchema = schema
964            for item in self.somWidgets():
965                item.setHistogramColorSchema(schema)
966           
967    def setComponentColorSchema(self, schema):
968        if schema is not self.componentColorSchema:
969            self.componentColorSchema = schema
970            for item in self.somWidgets():
971                item.setComponentColorSchema(schema)
972       
973    def setNodePen(self, pen):
974        for item in self.somWidgets():
975            item.somItem.setNodePen(pen)
976           
977    def mousePressEvent(self, event):
978        if event.button() == Qt.LeftButton:
979            self.selectionManager.start(event)
980            if getattr(self, "selectionRectItem", None) is not None:
981                self.removeItem(self.selectionRectItem)
982            self.selectionRectItem = self.addRect(self.selectionManager.lastSelectionRect())
983            self.selectionRectItem.setRect(self.selectionManager.lastSelectionRect())
984            self.selectionRectItem.show()
985       
986    def mouseMoveEvent(self, event):
987        if event.buttons() & Qt.LeftButton:
988            self.selectionManager.update(event)
989            if self.selectionRectItem:
990                self.selectionRectItem.setRect(self.selectionManager.lastSelectionRect())
991   
992    def mouseReleaseEvent(self, event):
993        if event.button() & Qt.LeftButton:
994            self.selectionManager.end(event)
995            if self.selectionRectItem:
996                self.selectionRectItem.hide()
997                self.removeItem(self.selectionRectItem)
998                self.selectionRectItem = None
999   
1000    def mouseDoubleClickEvent(self, event):
1001        return
1002   
1003    def onSelectionAreaChange(self):
1004        self.setSelectionArea(self.selectionManager.selectionArea())
1005       
1006    def updateToolTips(self, *args, **kwargs):
1007        for item in self.somWidgets():
1008            item.somItem.updateToolTips(*args, **kwargs)
1009           
1010           
1011class SOMComponentsWidget(QGraphicsWidget):
1012    def __init__(self, parent=None):
1013        QGraphicsWidget.__init__(self, parent)
1014        self.componentsLayout = QGraphicsGridLayout()
1015        self.setLayout(self.componentsLayout)
1016        self.components = []
1017        self.columns = 3
1018        self.shownComponents = []
1019        self.allComponents = []
1020        self.componentWidgets = {}
1021        self.colorPalette = None
1022        self.som = None
1023       
1024    def setSOM(self, som):
1025        self.removeComponents(self.components)
1026        self.shownComponents = []
1027        self.som = som
1028        self.allComponents = list(self.som.examples.domain.attributes)
1029       
1030    def addComponents(self, components):
1031        for c in components:
1032            if isinstance(c, orange.Variable):
1033                c = self.allComponents.index(c)
1034            self.shownComponents.append(c)
1035           
1036        self.updateComponents()
1037       
1038    def removeComponets(self, components):
1039        for c in components:
1040            if isinstance(c, orange.Variable):
1041                c = self.allComponents.index(c)
1042            if c in self.shownComponents:
1043#                self.removeComponet(component)
1044                self.shownComponents.remove(c)
1045                self.scene().removeItem(self.componentWidgets[c])
1046                del self.componentWidgets[c]
1047            else:
1048                print "not shown"
1049        self.updateComponents()
1050       
1051#    def removeComponent(self, component):
1052#        index = self.showComponents.index(component)
1053##        self.shownComponents.pop(index)
1054#        w = self.componentWidgets[c]
1055#        row, col = index / self.columns, index%self.columns
1056#       
1057#        self.layout().removeAt(self.layout().indexAt())
1058#        for i in range(index, len(self.shownComponents)):
1059
1060    def createComponentWidget(self, component):
1061        w = SOMGraphicsWidget()
1062        w.setSOM(self.som)
1063        w.setColorPalette(self.colorPalette)
1064             
1065           
1066    def updateComponents(self):
1067        self.shownComponents = sorted(set(self.shownComponents))
1068        self.setLayout(None)
1069        layout = QGraphicsGridLayout()
1070        self.setLayout(layout)
1071        for i, c in enumerate(self.shownComponents):
1072            if c in self.componentWidgets:
1073                w = self.componentWidgets[c]
1074            else:
1075                w = SOMGraphicsWidget()
1076                self.componentWidgets[c] = w
1077            layout.addItem(w, i/self.columns, i%self.columns)
1078           
1079   
1080    def somWidgets(self):
1081        for item in self.childItems():
1082            if isinstance(item, SOMGraphicsWidget):
1083                yield item
1084               
1085               
1086    def setHistogram(self, constructor):
1087        for item in self.somWidgets():
1088            item.setHistogram(constructor)
1089           
1090    def setColorPalette(self, palette):
1091        self.colorPalette = palette
1092        for item in self.somWidgets():
1093            item.setColorPalette(palette)
1094       
1095   
1096from OWItemModels import VariableListModel
1097class OWSOMVisualizerMk2(OWWidget):
1098    settingsList = ["drawMode", "showNodeOutlines", "objSize","commitOnChange", "backgroundMode", "backgroundCheck", "includeCodebook", "showToolTips"]
1099    contextHandlers = {"":DomainContextHandler("", [ContextField("attribute", DomainContextHandler.Optional),
1100                                                  ContextField("discHistMode", DomainContextHandler.Optional),
1101                                                  ContextField("contHistMode", DomainContextHandler.Optional),
1102                                                  ContextField("targetValue", DomainContextHandler.Optional),
1103                                                  ContextField("histogram", DomainContextHandler.Optional),
1104                                                  ContextField("inputSet", DomainContextHandler.Optional),
1105                                                  ContextField("component", DomainContextHandler.Optional),
1106                                                  ContextField("includeCodebook", DomainContextHandler.Optional)])}
1107   
1108    drawModes = ["None", "U-Matrix", "Component planes"]
1109   
1110    def __init__(self, parent=None, signalManager=None, name="SOM visualizer"):
1111        OWWidget.__init__(self, parent, signalManager, name, wantGraph=True)
1112        self.inputs = [("SOMMap", orngSOM.SOMMap, self.setSomMap), ("Examples", ExampleTable, self.data)]
1113        self.outputs = [("Examples", ExampleTable)]
1114       
1115        self.drawMode = 2
1116        self.objSize = 15
1117        self.component = 0
1118        self.labelNodes = 0
1119        self.commitOnChange = 0
1120        self.backgroundCheck = 1
1121        self.backgroundMode = 0
1122        self.includeCodebook = 0
1123        self.showToolTips = 0
1124        self.histogram = 1
1125        self.attribute = 0
1126        self.discHistMode = 0
1127        self.targetValue = 0
1128        self.contHistMode = 0
1129        self.inputSet = 0
1130        self.showNodeOutlines = 1
1131
1132        self.somMap = None
1133        self.examples = None
1134        self.selectionChanged = False
1135       
1136       
1137        self.scene = SOMScene(self)
1138        self.sceneView = QGraphicsView(self.scene, self.mainArea)
1139        self.sceneView.viewport().setMouseTracking(True)
1140        self.sceneView.setFocusPolicy(Qt.WheelFocus)
1141        self.sceneView.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing)
1142
1143        self.mainArea.layout().addWidget(self.sceneView)
1144        self.connect(self.scene, SIGNAL("selectionChanged()"), self.commitIf)
1145       
1146        self.loadSettings()
1147
1148        histTab = mainTab = self.controlArea
1149
1150        self.mainTab = mainTab
1151        self.histTab = histTab
1152
1153        self.backgroundBox = OWGUI.widgetBox(mainTab, "Background")
1154        b = OWGUI.radioButtonsInBox(self.backgroundBox, self, "drawMode", self.drawModes, callback=self.setBackground)
1155        b.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
1156        indent = OWGUI.indentedBox(self.backgroundBox, sep=OWGUI.checkButtonOffsetHint(b.buttons[-1]), addSpace=False)
1157        self.componentModel = VariableListModel([], self)
1158        self.componentView = QListView()
1159        self.componentView.setModel(self.componentModel)
1160        self.componentView.setSelectionMode(QListView.ExtendedSelection)
1161        self.connect(self.componentView.selectionModel(), SIGNAL("selectionChanged(QItemSelection, QItemSelection)"), lambda i1, i2: self.setBackground()) #self.onComponentPlaneSelection)
1162#        self.backgroundBox.layout().addWidget(self.componentView)
1163        indent.layout().addWidget(self.componentView)
1164        self.componentView.setEnabled(self.drawMode == 2)
1165#        self.componentCombo=OWGUI.comboBox(OWGUI.indentedBox(b), self,"component", callback=self.setBackground)
1166#        self.componentCombo.setEnabled(self.drawMode==2)
1167       
1168        OWGUI.checkBox(self.backgroundBox, self, "showNodeOutlines", "Show grid", callback=self.updateGrid)
1169       
1170        b = OWGUI.widgetBox(mainTab, "Histogram") 
1171        OWGUI.checkBox(b, self, "histogram", "Show histogram", callback=self.setHistogram)
1172        OWGUI.radioButtonsInBox(OWGUI.indentedBox(b), self, "inputSet", ["Use training set", "Use input subset"], callback=self.setHistogram)
1173       
1174        b1= OWGUI.widgetBox(mainTab) 
1175        b=OWGUI.hSlider(b1, self, "objSize","Plot size", 1, 100,step=10,ticks=10, callback=self.setZoom)
1176
1177        b1 = OWGUI.widgetBox(b1, "Tooltip Info")
1178        OWGUI.checkBox(b1, self, "showToolTips","Show tooltip", callback=self.updateToolTips)
1179        OWGUI.checkBox(b1, self, "includeCodebook", "Include codebook vector", callback=self.updateToolTips)
1180        b1.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
1181       
1182        self.histogramBox = OWGUI.widgetBox(histTab, "Coloring")
1183        self.attributeCombo = OWGUI.comboBox(self.histogramBox, self, "attribute", callback=self.onHistogramAttrSelection)
1184        self.coloringStackedLayout = QStackedLayout()
1185        indentedBox = OWGUI.indentedBox(self.histogramBox, orientation=self.coloringStackedLayout)
1186        self.discTab = OWGUI.widgetBox(indentedBox)
1187        OWGUI.radioButtonsInBox(self.discTab, self, "discHistMode", ["Pie chart", "Majority value", "Majority value prob."], box=0, callback=self.onHistogramAttrSelection)
1188        self.discTab.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
1189
1190        self.contTab = OWGUI.widgetBox(indentedBox)
1191        OWGUI.radioButtonsInBox(self.contTab, self, "contHistMode", ["Default", "Average value"], callback=self.onHistogramAttrSelection)
1192        self.contTab.layout().addStretch(10)
1193        self.contTab.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
1194 
1195        b = OWGUI.widgetBox(self.controlArea, "Selection")
1196        OWGUI.button(b, self, "&Invert selection", callback=self.invertSelection)
1197        button = OWGUI.button(b, self, "&Commit", callback=self.commit)
1198        check = OWGUI.checkBox(b, self, "commitOnChange", "Commit on change")
1199        OWGUI.setStopper(self, button, check, "selectionChanged", self.commit)
1200
1201        OWGUI.rubber(self.controlArea)
1202        self.connect(self.graphButton, SIGNAL("clicked()"), self.saveGraph)
1203       
1204        self.selectionList = []
1205        self.ctrlPressed = False
1206        self.resize(800,500)
1207
1208    def sendReport(self):
1209        self.reportSettings("Visual settings",
1210                           [("Background", self.drawModes[self.scene.drawMode]+(" - "+self.componentCombo.currentText() if self.scene.drawMode==2 else "")),
1211                            ("Histogram", ["data from training set", "data from input subset"][self.inputSet] if self.histogram else "none"),
1212                            ("Coloring",  "%s for %s" % (["pie chart", "majority value", "majority value probability"][self.discHistMode], self.attributeCombo.currentText()))
1213                           ])
1214        self.reportSection("Plot")
1215        self.reportImage(OWChooseImageSizeDlg(self.scene).saveImage)
1216       
1217       
1218    def getSelectedComponents(self):
1219        rows = self.componentView.selectionModel().selectedRows()
1220        rows = [r.row() for r in rows]
1221        return rows #[self.componentModel[r] for r in rows]
1222   
1223    def onComponentPlaneSelection(self, selected, unselected):
1224        selected = [s.row() for s in selected.indexes()]
1225        unselected = [u.row() for u in unselected.indexes()]
1226        for i in unselected:
1227            self.removeComponent(i)
1228        for i in selected:
1229            self.addComponent(i)
1230           
1231       
1232    def removeComponent(self, component):
1233        self.scene
1234       
1235       
1236   
1237    def setMode(self):
1238#        self.componentCombo.setEnabled(self.drawMode == 2)
1239        self.componentView.setEnabled(self.drawMode == 2)
1240        if not self.somMap:
1241            return
1242        self.error(0)
1243        if self.drawMode == 0:
1244            self.scene.setComponentPlanes([])
1245        elif self.drawMode == 2:
1246            self.scene.setComponentPlanes(self.getSelectedComponents()) #component)
1247        elif self.drawMode == 1:
1248            self.scene.setUMatrix()
1249        if self.histogram:
1250            self.setHistogram()
1251        self.updateToolTips()
1252        self.updateGrid()
1253        self.setZoom()
1254
1255    def setBackground(self):
1256        self.setMode()
1257
1258    def setDiscCont(self):
1259        if self.somMap.examples.domain.variables[self.attribute].varType == orange.VarTypes.Discrete:
1260            self.coloringStackedLayout.setCurrentWidget(self.discTab)
1261        else:
1262            self.coloringStackedLayout.setCurrentWidget(self.contTab)
1263       
1264    def onHistogramAttrSelection(self):
1265        if not self.somMap:
1266            return 
1267        if self.somMap.examples.domain.variables[self.attribute].varType == orange.VarTypes.Discrete:
1268            self.coloringStackedLayout.setCurrentWidget(self.discTab)
1269        else:
1270            self.coloringStackedLayout.setCurrentWidget(self.contTab)
1271           
1272        self.setHistogram()
1273       
1274    def setHistogramData(self, examples):
1275        for node in self.somMap:
1276            node.mappedExamples = orange.ExampleTable(examples.domain)
1277           
1278        for example in examples:
1279            bmn = self.somMap.getBestMatchingNode(example)
1280            bmn.mappedExamples.append(example)
1281           
1282    def setHistogram(self):
1283        if self.somMap and self.histogram:
1284            if self.inputSet and self.examples is not None and self.examples.domain == self.somMap.examples.domain:
1285                self.setHistogramData(self.examples)
1286#                self.scene.setHistogramData(self.examples)
1287                attr = self.examples.domain.variables[self.attribute]
1288            else:
1289                self.setHistogramData(self.somMap.examples)
1290#                self.scene.setHistogramData(self.somMap.examples)
1291                attr = self.somMap.examples.domain.variables[self.attribute]
1292               
1293            if attr.varType == orange.VarTypes.Discrete:
1294                hist = [PieChart, MajorityChart, MajorityProbChart]
1295                hist = hist[self.discHistMode]
1296                visible, schema = True, None
1297            else:
1298                hist = [ContChart, AverageValue]
1299                hist = hist[self.contHistMode]
1300                visible = self.contHistMode == 1
1301                schema = ColorPalette([(0, 255, 0), (255, 0, 0)]) if self.contHistMode == 1 else None
1302               
1303            def partial__init__(self, *args, **kwargs):
1304                hist.__init__(self, attr, *args)
1305               
1306            def partialLegendItem(cls, *args, **kwargs):
1307                return hist.legendItemConstructor(attr, *args, **kwargs)
1308            hist_ = type(hist.__name__ + "Partial", (hist,), {"__init__":partial__init__, "legendItemConstructor":classmethod(partialLegendItem)})
1309            if schema:
1310                self.scene.setHistogramColorSchema(schema)
1311            self.scene.setHistogramConstructor(hist_)
1312           
1313            if visible and schema:
1314                self.scene.somWidget.histLegendItem.setColorSchema(schema)
1315            elif not visible:
1316                self.scene.somWidget.histLegendItem.hide()
1317        else:
1318            self.scene.setHistogramConstructor(None)
1319           
1320    def update(self):
1321        self.setMode()
1322
1323    def drawPies(self):
1324        return self.discHistMode == 0 and self.somMap.examples.domain.variables[self.attribute].varType == orange.VarTypes.Discrete
1325   
1326    def setZoom(self):
1327        self.sceneView.setTransform(QTransform().scale(float(self.objSize)/SOMMapItem.objSize, float(self.objSize)/SOMMapItem.objSize))
1328   
1329    def updateGrid(self):
1330        self.scene.setNodePen(QPen(Qt.black, 1) if self.showNodeOutlines else QPen(Qt.NoPen))
1331   
1332    def updateToolTips(self):
1333        self.scene.updateToolTips(showToolTips=self.showToolTips, includeCodebook=self.includeCodebook)
1334       
1335    def setSomMap(self, somMap=None):
1336        self.somType = "Map"
1337        self.setSom(somMap)
1338       
1339    def setSomClassifier(self, somMap=None):
1340        self.somType = "Classifier"
1341        self.setSom(somMap)
1342       
1343    def setSom(self, somMap=None):
1344        self.closeContext()
1345        self.somMap = somMap
1346        if not somMap:
1347            self.clear()
1348            return
1349#        self.componentCombo.clear()
1350        self.componentModel[:] = somMap.examples.domain.attributes
1351        self.attributeCombo.clear()
1352       
1353        self.targetValue = 0
1354        self.attribute = 0
1355        self.component = 0
1356#        for v in somMap.examples.domain.attributes:
1357#            self.componentCombo.addItem(v.name)
1358        for v in somMap.examples.domain.variables:
1359            self.attributeCombo.addItem(v.name)
1360           
1361        if somMap.examples.domain.classVar:
1362            self.attribute = len(somMap.examples.domain.variables) - 1
1363
1364        self.openContext("", somMap.examples)
1365        self.component = min(self.component, len(somMap.examples.domain.attributes) - 1)
1366        self.attribute = min(self.attribute, len(somMap.examples.domain.variables) - 1)
1367        self.setDiscCont()
1368        self.scene.setSom(somMap)
1369        self.update()
1370#        self.componentView.selectionModel().select(self.componentModel.index(0), QItemSelectionModel.ClearAndSelect)
1371       
1372    def data(self, data=None):
1373        self.examples = data
1374        if data and self.somMap:
1375            for n in self.somMap.map:
1376                setattr(n,"mappedExamples", orange.ExampleTable(data.domain))
1377            for e in data:
1378                bmu = self.somMap.getBestMatchingNode(e)
1379                bmu.mappedExamples.append(e)
1380            if self.inputSet == 1:
1381                self.setHistogram()
1382        self.update()
1383   
1384    def clear(self):
1385        self.scene.clearSelection()
1386#        self.componentCombo.clear()
1387        self.componentModel[:] = []
1388        self.attributeCombo.clear()
1389        self.scene.component = 0
1390        self.scene.setSom(None)
1391        self.update()
1392        self.send("Examples", None)
1393       
1394    def invertSelection(self):
1395        self._invertingSelection = True
1396        try:
1397            for widget in self.scene.somWidgets():
1398                for node in widget.somItem.nodes():
1399                    node.setSelected(not node.isSelected())
1400        finally:
1401            del self._invertingSelection
1402            self.commitIf()
1403   
1404    def updateSelection(self, nodeList):
1405        self.selectionList = nodeList
1406        self.commitIf()
1407       
1408    def commitIf(self):
1409        if self.commitOnChange and not getattr(self, "_invertingSelection", False):
1410            self.commit()
1411        else:
1412            self.selectionChanged = True
1413           
1414    def commit(self):
1415        if not self.somMap:
1416            return
1417        ex = orange.ExampleTable(self.somMap.examples.domain)
1418       
1419        for n in self.scene.selectedItems():
1420            if isinstance(n, GraphicsSOMItem) and n.node and hasattr(n.node, "mappedExamples"):
1421                ex.extend(n.node.mappedExamples)
1422        if len(ex):
1423            self.send("Examples",ex)
1424        else:
1425            self.send("Examples",None)
1426           
1427        self.selectionChanged = False
1428
1429    def saveGraph(self):
1430        sizeDlg = OWChooseImageSizeDlg(self.scene, parent=self)
1431        sizeDlg.exec_()
1432       
1433if __name__=="__main__":
1434    ap = QApplication(sys.argv)
1435#    data = orange.ExampleTable("../../doc/datasets/housing.tab")
1436    data = orange.ExampleTable("../../doc/datasets/iris.tab")
1437#    l=orngSOM.SOMLearner(batch_train=False)
1438#    l = orngSOM.SOMLearner(batch_train=True, initialize=orngSOM.InitializeLinear)
1439#    l = orngSOM.SOMLearner(batch_train=True, initialize=orngSOM.InitializeRandom)
1440#    l = l(data)
1441#    l.data = data
1442    import cPickle
1443#    cPickle.dump(l, open("iris.som", "wb"))
1444    l = cPickle.load(open(os.path.expanduser("~/orange_som.pickle"), "rb"))
1445   
1446    w = OWSOMVisualizerMk2()
1447##    ap.setMainWidget(w)
1448    w.setSomClassifier(l)
1449    w.data(orange.ExampleTable(data[:]))
1450    w.show()
1451    ap.exec_()
1452    w.saveSettings()
1453   
1454