# source:orange/orange/OrangeWidgets/Classify/OWNomogramGraph.py@9273:9c33029c1c40

Revision 9273:9c33029c1c40, 78.0 KB checked in by ales_erjavec <ales.erjavec@…>, 2 years ago (diff)

More fixes to reporting.

Line
1# Nomogram visualization widget. It is used together with OWNomogram
2
3from OWWidget import *
4import numpy
5import math
6import time, statc
7import OWQCanvasFuncts
8
9# constants
10SE_Z = -100
11HISTOGRAM_Z = -200
12aproxZero = 0.0001
13
14def norm_factor(p):
15    max = 10.
16    min = -10.
17    z = 0.
18    eps = 0.001
19    while ((max-min)>eps):
20        pval = statc.zprob(z)
21        if pval>p:
22            max = z
23        if pval<p:
24            min = z
25        z = (max + min)/2.
26    return z
27
28def unique(lst):
29    d = {}
30    for item in lst:
31        d[item] = None
32    return d.keys()
33
34# returns difference between continuous label values
35def getDiff(d):
36    if d < 1 and d>0:
37        mnum = d/pow(10, math.floor(math.log10(d)))
38    else:
39        mnum = d
40
41    if d<1e-6:
42        return 0
43    if str(mnum)[0]>'4':
44        return math.pow(10,math.floor(math.log10(d))+1)
45    elif str(mnum)[0]<'2':
46        return 2*math.pow(10,math.floor(math.log10(d)))
47    else:
48        return 5*math.pow(10,math.floor(math.log10(d)))
49
50
51# Detailed description of selected value in attribute:
52#     - shows its value on the upper scale (points, log odds, ...)
53#     - its real value (example: age = 45) or in case of discrete attribute:
54#     - shows proportional value between two (real,possible) values of attribute
55class Descriptor(QGraphicsRectItem):
56    def __init__(self, canvas, attribute, z=60):
57        QGraphicsRectItem.__init__(self, None, canvas)
58        self.setPen(QPen(Qt.black, 2))
59        self.setBrush(QBrush(QColor(135,206,250)))
60        self.canvas = canvas
61        self.attribute = attribute
62
63        self.splitLine = OWQCanvasFuncts.OWCanvasLine(canvas, show = 0)
64        self.header = OWQCanvasFuncts.OWCanvasText(canvas, "", alignment = Qt.AlignLeft | Qt.AlignTop, show = 0)
65        self.headerValue = OWQCanvasFuncts.OWCanvasText(canvas, "", alignment = Qt.AlignRight | Qt.AlignTop, show = 0)
66        self.valName = OWQCanvasFuncts.OWCanvasText(canvas, "", alignment = Qt.AlignLeft | Qt.AlignTop, show = 0)
67        self.value = OWQCanvasFuncts.OWCanvasText(canvas, "", alignment = Qt.AlignRight | Qt.AlignTop, show = 0)
68        self.supportingValName = OWQCanvasFuncts.OWCanvasText(canvas, "", alignment = Qt.AlignLeft | Qt.AlignTop, show = 0)
69        self.supportingValue = OWQCanvasFuncts.OWCanvasText(canvas, "", alignment = Qt.AlignRight | Qt.AlignTop, show = 0)
70        self.setZAll(z)
71        self.hide()
72
73
74    def drawAll(self, x, y):
75        def getNearestAtt(selectedBeta):
76            if isinstance(self.attribute, AttrLineCont):
77                for i in range(len(self.attribute.attValues)):
78                    if self.attribute.attValues[i].betaValue==selectedBeta:
79                        nearestRight = self.attribute.attValues[i]
80                        nearestLeft = self.attribute.attValues[i]
81                        break
82                    elif i>0 and self.attribute.attValues[i].betaValue>selectedBeta and selectedBeta>self.attribute.attValues[i-1].betaValue:
83                        nearestRight = self.attribute.attValues[i]
84                        nearestLeft = self.attribute.attValues[i-1]
85                        break
86                    elif i>0 and self.attribute.attValues[i].betaValue<selectedBeta and selectedBeta<self.attribute.attValues[i-1].betaValue:
87                        nearestRight = self.attribute.attValues[i-1]
88                        nearestLeft = self.attribute.attValues[i]
89                        break
90                    elif i == len(self.attribute.attValues)-1:
91                        nearestRight = self.attribute.attValues[i]
92                        nearestLeft = self.attribute.attValues[i-1]
93                        break
94
95            else:
96                nearestLeft = filter(lambda x: x.betaValue == max([at.betaValue for at in filter(lambda x: x.betaValue <= selectedBeta, self.attribute.attValues)]) ,self.attribute.attValues)[0]
97                nearestRight = filter(lambda x: x.betaValue == min([at.betaValue for at in filter(lambda x: x.betaValue >= selectedBeta, self.attribute.attValues)]) ,self.attribute.attValues)[0]
98            return (nearestLeft, nearestRight)
99
100        # I need mapper to calculate various quantities (said in chemistry way) from the attribute and its selected value
101        # happens at the time of drawing header and footer canvases
102        # x and y should be on canvas!
103
104        if ((not isinstance(self.canvas, BasicNomogram) or not self.canvas.onCanvas(x,y)) and
105            (not isinstance(self.canvas, BasicNomogramFooter) or not self.canvas.onCanvas(x,y))):
106            return True
107
108        if isinstance(self.canvas, BasicNomogramFooter) and self.canvas.onCanvas(x,y):
111            self.valName.setPlainText("Value:")
112            if self.attribute.selectedValue:
113                self.value.setPlainText(str(round(self.attribute.selectedValue[2],2)))
114            else:
115                self.value.setPlainText("None")
116            self.supportingValName.setPlainText("")
117            self.supportingValue.setPlainText("")
118            points = 1
119        else:
120            # get points
121            if not self.attribute or not self.attribute.selectedValue:
122                return True
123            selectedBeta = self.attribute.selectedValue[2]
124            proportionalBeta = self.canvas.mapper.propBeta(selectedBeta, self.attribute)
125            maxValue = self.canvas.mapper.getMaxMapperValue()
126            minValue = self.canvas.mapper.getMinMapperValue()
127            points = minValue+(maxValue-minValue)*proportionalBeta
128
131
132
133            # continuous? --> get attribute value
134            if isinstance(self.attribute, AttrLineCont):
135                self.valName.setPlainText("Value:")
136                if len(self.attribute.selectedValue)==4:
137                    self.value.setPlainText(str(round(self.attribute.selectedValue[3],2)))
138                else:
139                    (nleft, nright) = getNearestAtt(selectedBeta)
140                    if nright.betaValue>nleft.betaValue:
141                        prop = (selectedBeta-nleft.betaValue)/(nright.betaValue-nleft.betaValue)
142                    else:
143                        prop = 0
144                    if prop == 0:
145                        avgValue = (float(nleft.name)+float(nright.name))/2.
146                    else:
147                        avgValue = float(nleft.name)+prop*(float(nright.name)-float(nleft.name))
148                    self.value.setPlainText(str(round(avgValue,2)))
149                self.supportingValName.setPlainText("")
150                self.supportingValue.setPlainText("")
151            # discrete? --> get left and right value, proportional select values
152            else:
153                (nleft, nright) = getNearestAtt(selectedBeta)
154                if nright.betaValue>nleft.betaValue:
155                    prop = (selectedBeta-nleft.betaValue)/(nright.betaValue-nleft.betaValue)
156                else:
157                    prop = 0
158                if prop == 0 or prop == 1:
159                    self.valName.setPlainText("Value:")
160                    self.supportingValName.setPlainText("")
161                    self.supportingValue.setPlainText("")
162                    if prop == 0:
163                        self.value.setPlainText(nleft.name)
164                    else:
165                        self.value.setPlainText(nright.name)
166                else:
167                    self.valName.setPlainText(nleft.name + ":")
168                    self.supportingValName.setPlainText(nright.name + ":")
169                    self.value.setPlainText(str(round(1-prop,4)*100)+"%")
170                    self.supportingValue.setPlainText(str(round(prop,4)*100)+"%")
171
172        # set height
173        height = 15+ self.valName.boundingRect().height() + self.header.boundingRect().height()
174        if str(self.supportingValName.toPlainText()) != "":
175            height+= self.supportingValName.boundingRect().height()
176
177        # set width
179                  self.valName.boundingRect().width()+2+self.value.boundingRect().width(),
180                  self.supportingValName.boundingRect().width()+2+self.supportingValue.boundingRect().width()])
181
182        # if bubble wants to jump of the canvas, better catch it !
183        selOffset = 20
184##        xTemp, yTemp = x+selOffset, y-selOffset-height
185        view = self.canvas.views()[0]
186        viewx = view.mapFromScene(x,y).x()
187        viewy = view.mapFromScene(x,y).y()
188        xTemp = viewx + selOffset
189        yTemp = viewy - selOffset - height
190        max_x = view.width()-50
191        max_y = view.height()
192        min_x = 0
193        min_y = 0
194        while not self.inRect(xTemp,yTemp,min_x,min_y,max_x,max_y) or \
195              not self.inRect(xTemp,yTemp+height,min_x,min_y,max_x,max_y) or \
196              not self.inRect(xTemp+width,yTemp,min_x,min_y,max_x,max_y) or \
197              not self.inRect(xTemp+width,yTemp+height,min_x,min_y,max_x,max_y):
198            if yTemp == viewy-selOffset-height and not xTemp <= viewx-selOffset-width:
199                xTemp-=1
200            elif xTemp <= viewx-selOffset-width and not yTemp >= viewy+selOffset:
201                yTemp+=1
202            elif yTemp >= viewy+selOffset and not xTemp >= viewx+selOffset:
203                xTemp+=1
204            elif xTemp>= viewx+selOffset and not yTemp<viewy-selOffset-height+2:
205                yTemp-=1
206            else:
207                break
208
209        x,y = view.mapToScene(xTemp, yTemp).x(), view.mapToScene(xTemp, yTemp).y()
210
211        # set coordinates
212        self.setRect(x,y,width+2, height+2)
213
217
218        #line
220
221        # values
226
227        #return false if position is at zero and alignment is centered
228        if round(points,3) == 0.0 and self.canvas.parent.alignType == 1:
229            return False
230        return True
231
232    def setZAll(self, z):
233        self.setZValue(z)
235        self.splitLine.setZValue(z+1)
237        self.valName.setZValue(z+1)
238        self.value.setZValue(z+1)
239        self.supportingValName.setZValue(z+1)
240        self.supportingValue.setZValue(z+1)
241
242    def showAll(self):
243        self.show()
244        self.splitLine.show()
247        self.valName.show()
248        self.value.show()
249        if str(self.supportingValName.toPlainText()) != "":
250            self.supportingValName.show()
251            self.supportingValue.show()
252
253    def hideAll(self):
254        self.hide()
256        self.splitLine.hide()
258        self.valName.hide()
259        self.value.hide()
260        self.supportingValName.hide()
261        self.supportingValue.hide()
262
263    def inRect(self,x,y,x1,y1,x2,y2):
264        if x < x1 or x > x2 or y < y1 or y > y2:
265            return False
266        return True
267
268
269# Attribute value selector -- a small circle
270class AttValueMarker(QGraphicsEllipseItem):
271    def __init__(self, attribute, canvas, z=50):
272        self.r = 5
273        apply(QGraphicsEllipseItem.__init__,(self,0,0,2*self.r,2*self.r,None,canvas))
274
275        #self.canvas = canvas
276        self.setZValue(z)
277        self.setBrush(QBrush(Qt.blue))
278        self.name=""
279        #self.borderCircle = QCanvasEllipse(15,15,canvas)
280        #self.borderCircle.setBrush(QBrush(Qt.red))
281        #self.borderCircle.setZValue(z-1)
282        self.descriptor = Descriptor(canvas, attribute, z+1)
283        self.hide()
284
285    def x(self):
286        return QGraphicsEllipseItem.x(self)+self.r
287
288    def y(self):
289        return QGraphicsEllipseItem.y(self)+self.r
290
291    def setPos(self, x, y):
292        QGraphicsEllipseItem.setPos(self, x-self.r, y-self.r)
293        #self.borderCircle.setX(x)
294        #self.borderCircle.setY(y)
295        if not self.descriptor.drawAll(x,y):
296            brush = QBrush(self.brush().color(), Qt.Dense4Pattern)
297            #brush.setStyle()
298            self.setBrush(brush)
299        else:
300            self.setBrush(QBrush(self.brush().color()))
301
302    def showSelected(self):
303        #self.borderCircle.show()
304        self.setBrush(QBrush(QColor(253,151,51), self.brush().style()))
305#        if self.canvas().parent.bubble:
306        self.descriptor.showAll()
307
308    def hideSelected(self):
309        #self.borderCircle.hide()
310        self.setBrush(QBrush(Qt.blue, self.brush().style()))
311        self.descriptor.hideAll()
312
313
314# ####################################################################
315# Single Attribute Value
316# ####################################################################
317class AttValue:
318    def __init__(self, name, betaValue, error=0, showErr=False, over=True, lineWidth = 0, markerWidth = 2, enable = True):
319        self.name = name
320        self.betaValue = betaValue
321        self.error = error
322        self.showErr = showErr
323        self.enable = enable
324        self.hideAtValue = False
325        self.over = over
326        self.lineWidth = lineWidth
327        self.markerWidth = markerWidth
328        self.attCreation = True # flag shows that vanvas object have to be created first
329
330    def destroy(self):
331        if not self.attCreation:
332            self.hide()
333
334    def setCreation(self, canvas):
335        self.text = OWQCanvasFuncts.OWCanvasText(canvas, self.name, alignment = Qt.AlignCenter, show = 0)
336        self.labelMarker = OWQCanvasFuncts.OWCanvasLine(canvas, 0, 0, 1, 1, penWidth = self.markerWidth, show = 0)
337        self.histogram = OWQCanvasFuncts.OWCanvasLine(canvas, 0, 0, 1, 1, penColor = QColor(140,140,140), penWidth = 7, z = HISTOGRAM_Z, show = 0)
338        self.errorLine = OWQCanvasFuncts.OWCanvasLine(canvas, 0, 0, 1, 1, penColor = QColor(25,25,255), penWidth = 1, z = SE_Z, show = 0)
339        self.attCreation = False
340
341    def hide(self):
342        self.text.hide()
343        self.labelMarker.hide()
344        self.errorLine.hide()
345
346    def paint(self, canvas, rect, mapper):
347        def errorCollision(line,z=SE_Z):
348            col = filter(lambda x:x.zValue()==z,line.collidingItems())
349            if len(col)>0:
350                return True
351            return False
352
353        if self.attCreation:
354            self.setCreation(canvas)
355        self.text.setPos(self.x, self.text.y)
356        if self.enable:
357            lineLength = canvas.fontSize/2
358            canvasLength = 0
359            if canvas.parent.histogram and isinstance(canvas, BasicNomogram):
360                canvasLength = 2+self.lineWidth*canvas.parent.histogram_size
361            if self.over:
362                self.text.setPos(self.x, rect.bottom()-4*canvas.fontSize/3)
363                self.labelMarker.setLine(self.x, rect.bottom(), self.x, rect.bottom()+lineLength)
364                self.histogram.setLine(self.x, rect.bottom(), self.x, rect.bottom()+canvasLength)
365            else:
366                self.text.setPos(self.x, rect.bottom()+4*canvas.fontSize/3)
367                self.labelMarker.setLine(self.x, rect.bottom(), self.x, rect.bottom()-lineLength)
368                self.histogram.setLine(self.x, rect.bottom(), self.x, rect.bottom()-canvasLength)
369            if not self.hideAtValue:
370                self.text.show()
371            else:
372                self.text.hide()
373            if canvas.parent.histogram:
374                self.histogram.show()
375#            else:
376#                self.histogram.hide()
377        # if value is disabled, draw just a symbolic line
378        else:
379            self.labelMarker.setLine(self.x, rect.bottom(), self.x, rect.bottom()+canvas.fontSize/4)
380            self.text.hide()
381
382        # show confidence interval
383        if self.showErr:
384            self.low_errorX = max(self.low_errorX, 0)
385            self.high_errorX = min(self.high_errorX, canvas.size().width())
386            if self.low_errorX == 0 and self.high_errorX == canvas.size().width():
387                self.errorLine.setPen(QPen(self.errorLine.pen().color(),self.errorLine.pen().width(),Qt.DotLine))
388            else:
389                self.errorLine.setPen(QPen(self.errorLine.pen().color(), self.errorLine.pen().width()))
390
391            if self.over:
393                n=0
395                while errorCollision(self.errorLine):
396                    n=n+1
399                    else:
402            else:
405                while errorCollision(self.errorLine):
408                    else:
411            self.errorLine.show()
412        self.labelMarker.show()
413
414    def toString(self):
415        return self.name, "beta =", self.betaValue
416
417
418# ####################################################################
419# Normal attribute - 1d
420# ####################################################################
421# This is a base class for representing all different possible attributes in nomogram.
422# Use it only for discrete/non-ordered values
423class AttrLine:
424    def __init__(self, name, canvas):
425        self.name = name
426        self.attValues = []
427        self.minValue = self.maxValue = 0
428        self.selectedValue = None
429        self.initialize(canvas)
430
432        if len(self.attValues)==0:
433            self.minValue = attValue.betaValue
434            self.maxValue = attValue.betaValue
435        else:
436            self.minValue = min(self.minValue, attValue.betaValue)
437            self.maxValue = max(self.maxValue, attValue.betaValue)
438        self.attValues.append(attValue)
439
440    def getHeight(self, canvas):
441        return canvas.parent.verticalSpacing
442
443    # Find the closest (selectable) point to mouse-clicked one.
444    def updateValueXY(self, x, y):
445        oldSelect = self.selectedValue
446        minXDiff = 50
447        minYDiff = 50
448        minAbs = 100
449        for xyCanvas in self.selectValues:
450            if (abs(x-xyCanvas[0]) + abs(y-xyCanvas[1]))<minAbs:
451                self.selectedValue = xyCanvas
452                minYDiff = abs(y-xyCanvas[1])
453                minXDiff = abs(x-xyCanvas[0])
454                minAbs = minYDiff + minXDiff
455        if oldSelect == self.selectedValue:
456            return False
457        else:
458            self.marker.setPos(self.selectedValue[0], self.selectedValue[1])
459            return True
460
461    # Update position of the marker!
462    # This is usualy necessary after changing types of nomogram, for example left-aligned to center-aligned.
463    # In this situations selected beta should stay the same, but x an y of the marker must change!
464    def updateValue(self):
465        if not self.selectedValue:
466            return
467        beta = self.selectedValue[2]
469        for xyCanvas in self.selectValues:
471                self.selectedValue = xyCanvas
473        self.marker.setPos(self.selectedValue[0], self.selectedValue[1])
474
475    def initialize(self, canvas):
476        def createText(name):
477            return OWQCanvasFuncts.OWCanvasText(canvas, name, bold = 1, show = 0)
478
479        if type(self.name) == str:
480            self.label = [createText(self.name)]
481        else:
482            self.label = [createText(n) for n in self.name]
483        self.line = OWQCanvasFuncts.OWCanvasLine(canvas, 0, 0, 1, 1, show = 0)
484
485        # create blue probability marker
486        self.marker = AttValueMarker(self, canvas, 50)
487
488    def drawAttributeLine(self, canvas, rect, mapper):
489        atValues_mapped, atErrors_mapped, min_mapped, max_mapped = mapper(self, error_factor = norm_factor(1-((1-float(canvas.parent.confidence_percent)/100.)/2.))) # return mapped values, errors, min, max --> mapper(self)
490        for l_i,l in enumerate(self.label):
491            l.setPos(1, rect.bottom()-(1-l_i)*canvas.fontSize+l_i*canvas.fontSize/3)
492
493        # draw attribute line
494        self.line.setLine(min_mapped, rect.bottom(), max_mapped, rect.bottom())
495        zero = 0
496        if len([at.betaValue for at in self.attValues]) == 0:
497            return
498        if min([at.betaValue for at in self.attValues])>0:
499            zero = min([at.betaValue for at in self.attValues])
500        if max([at.betaValue for at in self.attValues])<0:
501            zero = max([at.betaValue for at in self.attValues])
502        self.selectValues = [[mapper.mapBeta(zero, self), rect.bottom(), zero]]
503        if not self.selectedValue:
504            self.selectedValue = self.selectValues[0]
505
506    def paint(self, canvas, rect, mapper):
507##        self.label.setPlainText(self.name)
508        atValues_mapped, atErrors_mapped, min_mapped, max_mapped = mapper(self, error_factor = norm_factor(1-((1-float(canvas.parent.confidence_percent)/100.)/2.))) # return mapped values, errors, min, max --> mapper(self)
509
510        self.drawAttributeLine(canvas, rect, mapper)
511        # draw attributes
512        val = self.attValues
513
514        # draw values
515        for i in range(len(val)):
516            # check attribute name that will not cover another name
517            val[i].x = atValues_mapped[i]
518            val[i].high_errorX = atErrors_mapped[i][1]
519            val[i].low_errorX = atErrors_mapped[i][0]
520            if canvas.parent.confidence_check and val[i].error>0:
521                val[i].showErr = True
522            else:
523                val[i].showErr = False
524
525            val[i].hideAtValue = False
526            val[i].over = True
527            val[i].paint(canvas, rect, mapper)
528
529            #find suitable value position
530            for j in range(i):
531                #if val[j].over and val[j].enable and abs(atValues_mapped[j]-atValues_mapped[i])<(len(val[j].name)*canvas.fontSize/4+len(val[i].name)*canvas.fontSize/4):
532                if val[j].over and val[j].enable and not val[j].hideAtValue and val[j].text.collidesWithItem(val[i].text):
533                    val[i].over = False
534            if not val[i].over:
535                val[i].paint(canvas, rect, mapper)
536                for j in range(i):
537                    if not val[j].over and val[j].enable and not val[j].hideAtValue and val[j].text.collidesWithItem(val[i].text):
538                        val[i].hideAtValue = True
539                if val[i].hideAtValue:
540                    val[i].paint(canvas, rect, mapper)
541            self.selectValues.append([atValues_mapped[i], rect.bottom(), val[i].betaValue])
542
543        atLine = AttrLine("marker", canvas)
544        d = 5*(self.maxValue-self.minValue)/max((max_mapped-min_mapped),aproxZero)
545        for xc in numpy.arange(self.minValue, self.maxValue+d, max(d, aproxZero)):
547
548        markers_mapped, mark_errors_mapped, markMin_mapped, markMax_mapped = mapper(atLine)
549        for mar in range(len(markers_mapped)):
550            xVal = markers_mapped[mar]
551            if filter(lambda x: abs(x[0]-xVal)<4, self.selectValues) == [] and xVal<max_mapped:
552                self.selectValues.append([xVal, rect.bottom(), atLine.attValues[mar].betaValue])
553
554        self.updateValue()
555        if max_mapped - min_mapped > 5.0:
556            self.line.show()
557        [l.show() for l in self.label]
558
559    # some supplementary methods for 2d presentation
560    # draw bounding box around cont. attribute
561    def drawBox(self, min_mapped, max_mapped, rect):
562        # draw box
563        self.box.setRect(min_mapped, rect.top()+rect.height()/8, max_mapped-min_mapped, rect.height()*7/8)
564
565        # show att. name
566##        self.label.setPlainText(self.name)
567        for l_i,l in enumerate(self.label):
568            l.setPos(min_mapped, rect.top()+(l_i+1)*rect.height()/8)
569
570    # draws a vertical legend on the left side of the bounding box
571    def drawVerticalLabel(self, attLineLabel, min_mapped, mapped_labels, canvas):
572        for at in range(len(attLineLabel.attValues)):
573            # draw value
574            a = self.contLabel[at]
575            a.setPos(min_mapped-5, mapped_labels[at]-canvas.fontSize/2)
576            if attLineLabel.attValues[at].enable:
577                a.marker.setLine(min_mapped-2, mapped_labels[at], min_mapped+2, mapped_labels[at])
578                a.show()
579            # if value is disabled, draw just a symbolic line
580            else:
581                a.marker.setLine(min_mapped-1, mapped_labels[at], min_mapped+1, mapped_labels[at])
582            a.marker.show()
583
584    #finds proportionally where zero value is
585    def findZeroValue(self):
586        maxPos,zero = 1,0
587        while self.attValues[maxPos].betaValue!=0 and self.attValues[maxPos-1].betaValue!=0 and self.attValues[maxPos].betaValue/abs(self.attValues[maxPos].betaValue) == self.attValues[maxPos-1].betaValue/abs(self.attValues[maxPos-1].betaValue):
588            maxPos+=1
589            if maxPos == len(self.attValues):
590                maxPos-=1
591                zero = self.attValues[maxPos].betaValue
592                break
593        if not self.attValues[maxPos].betaValue == self.attValues[maxPos-1].betaValue:
594            return ((zero-self.attValues[maxPos-1].betaValue)/(self.attValues[maxPos].betaValue - self.attValues[maxPos-1].betaValue),maxPos,zero)
595        else:
596            return (zero,maxPos,zero)
597
598
599    # string representation of attribute
600    def toString(self):
601        return self.name + str([at.toString() for at in self.attValues])
602
603
604# ####################################################################
605# Continuous attribute in 2d
606# ####################################################################
607class AttrLineCont(AttrLine):
608    def __init__(self, name, canvas):
609        AttrLine.__init__(self, name, canvas)
610
611        # continuous attributes
612        self.cAtt = None
613        self.box = OWQCanvasFuncts.OWCanvasRectangle(canvas, pen = QPen(Qt.DotLine), show = 0)
614        self.contValues = []
615        self.contLabel = []
616
617    def getHeight(self, canvas):
618        if canvas.parent.contType == 1:
619            return canvas.parent.verticalSpacingContinuous
620        return AttrLine.getHeight(self, canvas)
621
622    # initialization before 2d paint
623    def initializeBeforePaint(self, canvas):
624        [l.scene().removeItem(l) for l in self.contLabel]
625        self.atNames = AttrLine(self.name, canvas)
626        for at in self.attValues:
628        verticalRect = QRect(0, 0, self.getHeight(canvas), self.getHeight(canvas))
629        verticalMapper = Mapper_Linear_Fixed(self.atNames.minValue, self.atNames.maxValue, verticalRect.left()+verticalRect.width()/4, verticalRect.right(), maxLinearValue = self.atNames.maxValue, minLinearValue = self.atNames.minValue)
630        label = verticalMapper.getHeaderLine(canvas, QRect(0,0,self.getHeight(canvas), self.getHeight(canvas)))
631        self.contLabel=[]
632        for val in label.attValues:
633            # draw value
634            a = OWQCanvasFuncts.OWCanvasText(canvas, val.name, alignment = Qt.AlignRight, show = 0)
635            a.marker = OWQCanvasFuncts.OWCanvasLine(canvas, z = 5, show = 0)
636            self.contLabel.append(a)
637
638        #line objects
639        if len(self.contValues) == 0:
640            for at in self.attValues:
641                a = OWQCanvasFuncts.OWCanvasLine(canvas, penWidth = at.lineWidth, show = 0)
642                a.upperSE = OWQCanvasFuncts.OWCanvasLine(canvas, penColor = Qt.blue, penWidth = 0, show = 0)
643                a.lowerSE = OWQCanvasFuncts.OWCanvasLine(canvas, penColor = Qt.blue, penWidth = 0, show = 0)
644                self.contValues.append(a)
645
646                # for 1d cont space
647                at.setCreation(canvas)
648
649
650    def paint(self, canvas, rect, mapper):
651##        self.label.setText(self.name)
652        atValues_mapped, atErrors_mapped, min_mapped, max_mapped = mapper(self, error_factor = norm_factor(1-((1-float(canvas.parent.confidence_percent)/100.)/2.))) # return mapped values, errors, min, max --> mapper(self)
653
654        self.drawAttributeLine(canvas, rect, mapper)
655
656        # continuous attributes are handled differently
657        self.cAtt = self.shrinkSize(canvas, rect, mapper)
658        atValues_mapped, atErrors_mapped, min_mapped, max_mapped = mapper(self.cAtt) # return mapped values, errors, min, max --> mapper(self)
659        val = self.cAtt.attValues
660        for i in range(len(val)):
661            # check attribute name that will not cover another name
662            val[i].x = atValues_mapped[i]
663            val[i].paint(canvas, rect, mapper)
664            for j in range(i):
665                if val[j].over==val[i].over and val[j].enable and val[i].text.collidesWithItem(val[j].text):
666                    val[i].enable = False
667            if not val[i].enable:
668                val[i].paint(canvas, rect, mapper)
669            self.selectValues.append([atValues_mapped[i], rect.bottom(), val[i].betaValue])
670
671        atLine = AttrLine("marker", canvas)
672        d = 5*(self.cAtt.maxValue-self.cAtt.minValue)/max(max_mapped-min_mapped,aproxZero)
673        for xc in numpy.arange(self.cAtt.minValue, self.cAtt.maxValue+d, max(d, aproxZero)):
675
676        markers_mapped, mark_errors_mapped, markMin_mapped, markMax_mapped = mapper(atLine)
677        for mar in range(len(markers_mapped)):
678            xVal = markers_mapped[mar]
679            if filter(lambda x: abs(x[0]-xVal)<4, self.selectValues) == [] and xVal<max_mapped:
680                self.selectValues.append([xVal, rect.bottom(), atLine.attValues[mar].betaValue])
681
682        self.updateValue()
683        self.line.show()
684        [l.show() for l in self.label]
685
686
687    # create an AttrLine object from a continuous variable (to many values for a efficient presentation)
688    def shrinkSize(self, canvas, rect, mapper):
689        # get monotone subset of this continuous variable
690        monotone_subsets, curr_subset = [],[]
691        sign=1
692        for at_i in range(len(self.attValues)):
693            if at_i<len(self.attValues)-1 and sign*(self.attValues[at_i].betaValue-self.attValues[at_i+1].betaValue)>0:
694                curr_subset.append(self.attValues[at_i])
695                monotone_subsets.append(curr_subset)
696                curr_subset = [self.attValues[at_i]]
697                sign=-sign
698            else:
699                curr_subset.append(self.attValues[at_i])
700        monotone_subsets.append(curr_subset)
701
702        # create retAttr --> values in between can be easily calculated from first left and first right
703        retAttr = AttrLine(self.name, canvas)
704        for at in self.attValues:
705            if at.betaValue == self.minValue or at.betaValue == self.maxValue:
706                at.enable = True
708        curr_over = False
709
710        # convert monotone subsets to nice step-presentation
711        for m in monotone_subsets:
712            if len(m)<2:
713                continue
714            curr_over = not curr_over
715            maxValue = max(float(m[0].name), float(m[len(m)-1].name))
716            minValue = min(float(m[0].name), float(m[len(m)-1].name))
717            width = mapper.mapBeta(max(m[0].betaValue, m[len(m)-1].betaValue),self) - mapper.mapBeta(min(m[0].betaValue, m[len(m)-1].betaValue),self)
718            curr_rect = QRect(rect.left(), rect.top(), width, rect.height())
719            mapperCurr = Mapper_Linear_Fixed(minValue, maxValue, curr_rect.left(), curr_rect.right(), maxLinearValue = maxValue, minLinearValue = minValue)
721            for at in label.attValues:
722                if at.betaValue>=minValue and at.betaValue<=maxValue:
723                    for i in range(len(m)):
724                        if i<(len(m)-1):
725                            if float(m[i].name)<=at.betaValue and float(m[i+1].name)>=at.betaValue:
726                                coeff = (at.betaValue-float(m[i].name))/max(float(m[i+1].name)-float(m[i].name),aproxZero)
728                                retAttr.attValues[len(retAttr.attValues)-1].over = curr_over
729
730        return retAttr
731
732    def paint2d(self, canvas, rect, mapper):
733        self.initializeBeforePaint(canvas)
734##        self.label.setText(self.name)
735
736        # get all values tranfsormed with current mapper
737        atValues_mapped, atErrors_mapped, min_mapped, max_mapped = mapper(self, error_factor = norm_factor(1-((1-float(canvas.parent.confidence_percent)/100.)/2.))) # return mapped values, errors, min, max --> mapper(self)
738
739        # draw a bounding box
740        self.drawBox(min_mapped, max_mapped, rect)
741
742        #draw legend from real values
743        verticalRect = QRect(rect.top(), rect.left(), rect.height(), rect.width())
744        verticalMapper = Mapper_Linear_Fixed(self.atNames.minValue, self.atNames.maxValue, verticalRect.left()+verticalRect.width()/4, verticalRect.right(), maxLinearValue = self.atNames.maxValue, minLinearValue = self.atNames.minValue, inverse=True)
746        mapped_labels, error, min_lab, max_lab = verticalMapper(label) # return mapped values, errors, min, max --> mapper(self)
747        self.drawVerticalLabel(label, min_mapped, mapped_labels, canvas)
748
749        #create a vertical mapper
750        atValues_mapped_vertical, atErrors_mapped_vertical, min_mapped_vertical, max_mapped_vertical = verticalMapper(self.atNames) # return mapped values, errors, min, max --> mapper(self)
751
752        #find and select zero value (beta = 0)
753        (propBeta,maxPos,zero) = self.findZeroValue()
754        zeroValue = float(self.attValues[maxPos-1].name) + propBeta*(float(self.attValues[maxPos].name) - float(self.attValues[maxPos-1].name))
755        self.selectValues = [[mapper.mapBeta(zero, self),verticalMapper.mapBeta(zeroValue, self.atNames), zero, zeroValue]]
756
757        if not self.selectedValue:
758            self.selectedValue = self.selectValues[0]
759
760        # draw lines
761        for i in range(len(atValues_mapped)-1):
762            a = self.contValues[i]
763            if canvas.parent.histogram:
764                a.setPen(QPen(Qt.black, 1+self.attValues[i].lineWidth*canvas.parent.histogram_size))
765            else:
766                a.setPen(QPen(Qt.black, 2))
767            #if self.attValues[i].lineWidth>0:
768            a.setLine(atValues_mapped[i], atValues_mapped_vertical[i], atValues_mapped[i+1], atValues_mapped_vertical[i+1])
769            a.upperSE.setLine(atErrors_mapped[i][0], atValues_mapped_vertical[i], atErrors_mapped[i+1][0], atValues_mapped_vertical[i+1])
770            a.lowerSE.setLine(atErrors_mapped[i][1], atValues_mapped_vertical[i], atErrors_mapped[i+1][1], atValues_mapped_vertical[i+1])
771            self.selectValues.append([atValues_mapped[i],atValues_mapped_vertical[i], self.attValues[i].betaValue, self.atNames.attValues[i].betaValue])
772
773
774            # if distance between i and i+1 is large, add some select values.
775            n = int(math.sqrt(math.pow(atValues_mapped[i+1]-atValues_mapped[i],2)+math.pow(atValues_mapped_vertical[i+1]-atValues_mapped_vertical[i],2)))/5-1
776            self.selectValues = self.selectValues + [[atValues_mapped[i]+(float(j+1)/float(n+1))*(atValues_mapped[i+1]-atValues_mapped[i]),
777                                          atValues_mapped_vertical[i]+(float(j+1)/float(n+1))*(atValues_mapped_vertical[i+1]-atValues_mapped_vertical[i]),
778                                          self.attValues[i].betaValue+(float(j+1)/float(n+1))*(self.attValues[i+1].betaValue-self.attValues[i].betaValue),
779                                          self.atNames.attValues[i].betaValue+(float(j+1)/float(n+1))*(self.atNames.attValues[i+1].betaValue-self.atNames.attValues[i].betaValue)] for j in range(n)]
780            a.show()
781
782            if canvas.parent.confidence_check:
783                a.upperSE.show()
784                a.lowerSE.show()
785            else:
786                a.upperSE.hide()
787                a.lowerSE.hide()
788
789        self.updateValue()
790        self.box.show()
791        [l.show() for l in self.label]
792
793
794
795# ####################################################################
796# Ordered attribute in 2d
797# ####################################################################
798class AttrLineOrdered(AttrLine):
799    def __init__(self, name, canvas):
800        AttrLine.__init__(self, name, canvas)
801
802        # continuous attributes
803        self.box = OWQCanvasFuncts.OWCanvasRectangle(canvas, pen = QPen(Qt.DotLine), show = 0)
804        self.contValues = []
805        self.contLabel = []
806
807    def getHeight(self, canvas):
808        if canvas.parent.contType == 1:
809            return len(self.attValues)*canvas.parent.diff_between_ordinal+canvas.parent.diff_between_ordinal
810        return AttrLine.getHeight(self, canvas)
811
812
813    # initialization before 2d paint
814    def initializeBeforePaint(self, canvas):
815        [l.scene().removeItem(l) for l in self.contLabel]
816        self.contLabel=[]
817        for val in self.attValues:
818            # draw value
819            a = OWQCanvasFuncts.OWCanvasText(canvas, val.name, alignment = Qt.AlignRight, show = 0)
820            a.marker = OWQCanvasFuncts.OWCanvasLine(canvas, z = 5, show = 0)
821            self.contLabel.append(a)
822
823        #line objects
824        if len(self.contValues) == 0:
825            for at in self.attValues:
826                a = OWQCanvasFuncts.OWCanvasLine(canvas, penWidth = at.lineWidth, show = 0)
827                self.contValues.append(a)
828
829                # for 1d cont space
830                at.setCreation(canvas)
831
832
833    def getVerticalCoordinates(self, rect, val):
834        return rect.bottom() - val.verticalDistance
835
836    def paint2d_fixedDistance(self, canvas, rect, mapper):
837        d = canvas.parent.diff_between_ordinal/2
838        for at in self.attValues:
839            at.verticalDistance = d
840            d += canvas.parent.diff_between_ordinal
841
842        # get all values tranfsormed with current mapper
843        atValues_mapped, atErrors_mapped, min_mapped, max_mapped = mapper(self) # return mapped values, errors, min, max --> mapper(self)
844
845        mapped_labels = [self.getVerticalCoordinates(rect,v)-canvas.fontSize/2 for v in self.attValues]
846        self.drawVerticalLabel(self, min_mapped, mapped_labels, canvas)
847
848        #find and select zero value (beta = 0)
849        (propBeta,maxPos,zero) = self.findZeroValue()
850        self.selectValues = [[mapper.mapBeta(zero, self),self.getVerticalCoordinates(rect, self.attValues[maxPos-1]), zero]]
851
852        if not self.selectedValue:
853            self.selectedValue = self.selectValues[0]
854
855        # draw lines
856        for i in range(len(atValues_mapped)):
857            a = self.contValues[i]
858            if canvas.parent.histogram:
859                a.setPen(QPen(Qt.black, 1+self.attValues[i].lineWidth*canvas.parent.histogram_size))
860            else:
861                a.setPen(QPen(Qt.black, 2))
862            a.setLine(atValues_mapped[i], self.getVerticalCoordinates(rect, self.attValues[i])-canvas.parent.diff_between_ordinal/2, atValues_mapped[i], self.getVerticalCoordinates(rect, self.attValues[i])+canvas.parent.diff_between_ordinal/2)
863            self.selectValues.append([atValues_mapped[i],self.getVerticalCoordinates(rect, self.attValues[i]), self.attValues[i].betaValue])
864            if i < len(atValues_mapped)-1:
865                a.connection = OWQCanvasFuncts.OWCanvasLine(canvas, pen = QPen(Qt.DotLine), show = 0)
866                a.connection.setLine(atValues_mapped[i],
867                                       self.getVerticalCoordinates(rect, self.attValues[i])-canvas.parent.diff_between_ordinal/2,
868                                       atValues_mapped[i+1],
869                                       self.getVerticalCoordinates(rect, self.attValues[i])-canvas.parent.diff_between_ordinal/2)
870                a.connection.show()
871            # if distance between i and i+1 is large, add some select values.
872            x1 = atValues_mapped[i]
873            y1 = self.getVerticalCoordinates(rect, self.attValues[i])-canvas.parent.diff_between_ordinal/2
874            x2 = atValues_mapped[i]
875            y2 = self.getVerticalCoordinates(rect, self.attValues[i])+canvas.parent.diff_between_ordinal/2
876
877            n = int(y2-y1)/5-1
878            self.selectValues = self.selectValues + [[x1, y1+(float(j+1)/float(n+1))*(y2-y1), self.attValues[i].betaValue] for j in range(n)]
879            a.show()
880
881
882    def paint2d(self, canvas, rect, mapper):
883        self.initializeBeforePaint(canvas)
884
885        # get all values tranfsormed with current mapper
886        atValues_mapped, atErrors_mapped, min_mapped, max_mapped = mapper(self) # return mapped values, errors, min, max --> mapper(self)
887
888        # draw a bounding box
889        self.drawBox(min_mapped, max_mapped+1, rect)
890
891        # if fixedDistance:
892        self.paint2d_fixedDistance(canvas, rect, mapper)
893
894
895        self.updateValue()
896        self.box.show()
897        [l.show() for l in self.label]
898
899
900# ####################################################################
902# ####################################################################
904    def __init__(self, nomogram, parent):
905        QGraphicsScene.__init__(self, parent)
906        self.initVars(nomogram, parent)
907
908    def initVars(self, nomogram, parent):
909        self.fontSize = parent.fontSize
911        self.nomogram = nomogram
912        self.parent = parent
913
914    def destroy_and_init(self, nomogram, parent):
915        self.destroy()
916        self.initVars(nomogram, parent)
917
918    def destroy(self):
919        for item in self.items():
920            if hasattr(item, "attribute"):
921                item.attribute = None
922            self.removeItem(item)
923
925        rect = QRect(rect)
926        # The header line follows the bottom of the rect.
927        rect.setBottom(30)
931#        self.resize(self.nomogram.pright, rect.height()+16)
932        self.update()
933
934
935# ####################################################################
936# FOOTER CANVAS, sum and probability
937# ####################################################################
938class BasicNomogramFooter(QGraphicsScene):
939    def __init__(self, nomogram, parent):
940        QGraphicsScene.__init__(self, parent)
941        self.initVars(nomogram, parent)
942
943    def onCanvas(self, x, y):
944        if x > self.width() or y > self.height(): return 0
945        else: return 1
946
947    def initVars(self, nomogram, parent):
948        self.fontSize = parent.fontSize
950        self.nomogram = nomogram
951        self.footer = None
952        self.footerPercent = None
953        self.parent = parent
954        if self.parent.cl:
955            self.footerPercentName = "P(%s=\"%s\")" % (self.parent.cl.domain.classVar.name,self.parent.cl.domain.classVar.values[self.parent.TargetClassIndex])
956        else:
957            self.footerPercentName = ""
958        self.connectedLine = OWQCanvasFuncts.OWCanvasLine(self, penColor = Qt.blue, show = 0)
959        self.errorLine = OWQCanvasFuncts.OWCanvasLine(self, pen = QPen(Qt.blue, 3), show = 0, z = 100)
960
961        self.errorPercentLine = OWQCanvasFuncts.OWCanvasLine(self, show = 0)
962        self.leftArc = OWQCanvasFuncts.OWCanvasEllipse(self, pen = QPen(Qt.blue, 3), z = 100, show = 0)
963        self.rightArc = OWQCanvasFuncts.OWCanvasEllipse(self, pen = QPen(Qt.blue, 3), z = 100, show = 0)
964        self.leftPercentArc = OWQCanvasFuncts.OWCanvasEllipse(self, pen = QPen(Qt.blue, 3), z = 100, show = 0)
965        self.rightPercentArc = OWQCanvasFuncts.OWCanvasEllipse(self, pen = QPen(Qt.blue, 3), z = 100, show = 0)
966        self.cilist = [self.errorLine, self.errorPercentLine, self.leftArc, self.rightArc, self.leftPercentArc, self.rightPercentArc]
967
970
971    def destroy_and_init(self, nomogram, parent):
972        for item in self.items():
973            if hasattr(item, "attribute"):
974                item.attribute = None
975            self.removeItem(item)
976
977        self.initVars(nomogram, parent)
978
979    def logit(self, val):
980        try:
981            return math.exp(val)/(1+math.exp(val))
982        except OverflowError:
983            return 1.0
984
985    def invLogit(self, p):
986        return math.log(p/max(1-p,aproxZero))
987
988    def convertToPercent(self, atLine):
991
992        percentLine = AttrLine(atLine.name, self)
993        percentList = filter(lambda x:x>minPercent and x<maxPercent,numpy.arange(0, maxPercent+0.1, 0.05))
994        for p in percentList:
995            if int(10*p) != round(10*p,1) and not p == percentList[0] and not p==percentList[len(percentList)-1]:
997            else:
999        return percentLine
1000
1001
1002    def paintFooter(self, rect, alignType, yAxis, mapper):
1003        # set height for each scale
1004        height = rect.height()/3
1005
1006        # get min and maximum sum, min and maximum beta
1007        # min beta <--> min sum! , same for maximum
1008        maxSum = minSum = maxSumBeta = minSumBeta = 0
1009        for at in self.nomogram.attributes:
1010            maxSum += mapper.getMaxValue(at)
1011            minSum += mapper.getMinValue(at)
1012            maxSumBeta += at.maxValue
1013            minSumBeta += at.minValue
1014
1015        # add constant to betas!
1016        maxSumBeta += self.nomogram.constant.betaValue
1017        minSumBeta += self.nomogram.constant.betaValue
1018
1019        # show only reasonable values
1020        k = (maxSum-minSum)/max((maxSumBeta-minSumBeta),aproxZero)
1021        if maxSumBeta>4:
1022            maxSum = (4 - minSumBeta)*k + minSum
1023            maxSumBeta = 4
1024        if minSumBeta>3:
1025            minSum = (3 - minSumBeta)*k + minSum
1026            minSumBeta = 3
1027        if minSumBeta<-4:
1028            minSum = (-4 - minSumBeta)*k + minSum
1029            minSumBeta = -4
1030        if maxSumBeta<-3:
1031            maxSum = (-3 - minSumBeta)*k + minSum
1032            maxSumBeta = -3
1033
1034        # draw continous line with values from min and max sum (still have values!)
1035        self.m = Mapper_Linear_Fixed(minSumBeta, maxSumBeta, rect.left(), rect.right(), maxLinearValue = maxSum, minLinearValue = minSum)
1036        if self.footer:
1037            for item in self.items():
1038                self.removeItem(item)
1039            #self.footer.destroy()
1040        self.footer = self.m.getHeaderLine(self, QRect(rect.left(), rect.top(), rect.width(), height))
1041        self.footer.name = self.nomogram.parent.totalPointsName[self.nomogram.parent.yAxis]
1042
1043        self.footer.paint(self, QRect(rect.left(), rect.top(), rect.width(), height), self.m)
1044
1045        # continous line convert to percent and draw accordingly (minbeta = minsum)
1046        #if self.footerPercent:
1047        #    self.footerPercent.destroy()
1048
1049        self.footerPercent = self.convertToPercent(self.footer)
1050
1051        # create a mapper for footer, BZ CHANGE TO CONSIDER THE TARGET
1052        self.footerPercent.name = self.footerPercentName
1053        self.footerPercent.paint(self, QRect(rect.left(), rect.top()+height, rect.width(), 2*height), self.m)
1054
1055#        self.resize(self.nomogram.pright, rect.height()+30)
1056        self.update()
1057
1058    def updateMarkers(self):
1059        # finds neares beta; use only discrete data
1060        def getNearestAtt(selectedBeta, at):
1061            nearestLeft = filter(lambda x: x.betaValue == max([v.betaValue for v in filter(lambda x: x.betaValue <= selectedBeta, at.attValues)]) ,at.attValues)[0]
1062            nearestRight = filter(lambda x: x.betaValue == min([v.betaValue for v in filter(lambda x: x.betaValue >= selectedBeta, at.attValues)]) ,at.attValues)[0]
1063            return (nearestLeft, nearestRight)
1064
1065        sum = self.nomogram.constant.betaValue
1066        for at in self.nomogram.attributes:
1067            sum += at.selectedValue[2]
1068
1069        variance = math.pow(self.nomogram.constant.error,2)
1070        for at in self.nomogram.attributes:
1071#            if not isinstance(at, AttrLineCont):
1072            if at.selectedValue[2] == 0.0 and self.parent.alignType == 1:
1073                continue
1074            (nleft, nright) = getNearestAtt(at.selectedValue[2], at)
1075            if nright.betaValue>nleft.betaValue:
1076                prop = (at.selectedValue[2]-nleft.betaValue)/(nright.betaValue-nleft.betaValue)
1077            else:
1078                prop = 0
1079            if prop == 0:
1080                variance += math.pow(nleft.error, 2)
1081            elif prop == 1:
1082                variance += math.pow(nright.error, 2)
1083            else:
1084                variance += math.pow(nleft.error, 2)*(1-prop)
1085                variance += math.pow(nright.error, 2)*prop
1086
1087        standard_error = math.sqrt(variance)
1088
1089        ax=self.m.mapBeta(sum, self.footer)
1090        # get CI
1091        ax_maxError = self.m.mapBeta(sum+standard_error*norm_factor(1-((1-float(self.parent.confidence_percent)/100.)/2.)), self.footer)
1092        ax_minError = self.m.mapBeta(sum-standard_error*norm_factor(1-((1-float(self.parent.confidence_percent)/100.)/2.)), self.footer)
1093        self.leftArc.setRect(ax_minError, self.footer.marker.y()+10, 10, 10)
1094        self.leftArc.setStartAngle(0)
1095        self.leftArc.setSpanAngle(180*16)
1096        self.leftArc.setBrush(QBrush(Qt.blue))
1097
1098        self.rightArc.setRect(ax_maxError-10, self.footer.marker.y()-5, 10, 10)
1099        self.rightArc.setStartAngle(90*16)
1100        self.rightArc.setSpanAngle(-90*16)
1101        self.leftPercentArc.setRect(ax_minError, self.footerPercent.marker.y()-5, 10, 10)
1102        self.leftPercentArc.setStartAngle(90*16)
1103        self.leftPercentArc.setSpanAngle(180*16)
1104        self.rightPercentArc.setRect(ax_maxError-10, self.footerPercent.marker.y()-5, 10, 10)
1105        self.rightPercentArc.setStartAngle(90*16)
1106        self.rightPercentArc.setSpanAngle(-90*16)
1107
1108        axPercentMin=self.m.mapBeta(self.footerPercent.minValue, self.footer)
1109        axPercentMax=self.m.mapBeta(self.footerPercent.maxValue, self.footer)
1110        axMin=self.m.mapBeta(self.footer.minValue, self.footer)
1111        axMax=self.m.mapBeta(self.footer.maxValue, self.footer)
1112
1113        ax = max(ax, axMin)
1114        ax = min(ax, axMax)
1115        self.errorLine.setLine(ax_minError, self.footer.marker.y(), ax_maxError, self.footer.marker.y())
1116        ax_minError = min(ax_minError, axPercentMax)
1117        ax_minError = max(ax_minError, axPercentMin)
1118        ax_maxError = min(ax_maxError, axPercentMax)
1119        ax_maxError = max(ax_maxError, axPercentMin)
1120
1121        self.errorPercentLine.setLine(ax_minError, self.footerPercent.marker.y(), ax_maxError, self.footerPercent.marker.y())
1122        #self.errorPercentLine.show()
1123
1124        self.footer.selectedValue = [ax,self.footer.marker.y(),self.m.mapBetaToLinear(sum, self.footer)]
1125        self.footer.marker.setPos(ax, self.footer.marker.y())
1126
1127        if ax>axPercentMax:
1128            ax=axPercentMax
1129        if ax<axPercentMin:
1130            ax=axPercentMin
1131        self.footerPercent.selectedValue = [ax,self.footer.marker.y(),1/(1+math.exp(-sum))]
1132        self.footerPercent.marker.setPos(ax, self.footerPercent.marker.y())
1133
1134        if self.parent.probability:
1135            self.footer.marker.show()
1136            self.footerPercent.marker.show()
1137            if self.footer.marker.x() == self.footerPercent.marker.x():
1138                self.connectedLine.setLine(self.footer.marker.x(), self.footer.marker.y(), self.footerPercent.marker.x(), self.footerPercent.marker.y())
1139                self.connectedLine.show()
1140            else:
1141                self.connectedLine.hide()
1142            if self.parent.confidence_check:
1143                self.showCI()
1144            else:
1145                self.hideCI()
1146        self.update()
1147
1148    def showCI(self):
1149        self.errorLine.show()
1150        self.errorPercentLine.show()
1151
1152    def hideCI(self):
1153        self.errorLine.hide()
1154        self.errorPercentLine.hide()
1155        self.leftArc.hide()
1156        self.rightArc.hide()
1157        self.leftPercentArc.hide()
1158        self.rightPercentArc.hide()
1159
1160
1161# ####################################################################
1162# Main CANVAS
1163# ####################################################################
1164class BasicNomogram(QGraphicsScene):
1165    def __init__(self, parent, constant, *args):
1166        QGraphicsScene.__init__(self, parent)
1167
1168        self.initVars(parent, constant)
1169
1170        self.parent=parent
1172        self.footerCanvas = BasicNomogramFooter(self, parent)
1174        self.parent.footer.setScene(self.footerCanvas)
1175
1176    def onCanvas(self, x, y):
1177        if x > self.width() or y > self.height(): return 0
1178        else: return 1
1179
1180    def initVars(self, parent, constant):
1181        self.attributes = []
1182        self.constant = constant
1183        self.minBeta = 0
1184        self.maxBeta = 0
1185        self.max_difference = 0
1186
1187        self.fontSize = parent.fontSize
1188        self.zeroLine = OWQCanvasFuncts.OWCanvasLine(self, 0,0,100,100,pen = QPen(QBrush(Qt.black), 1, Qt.DotLine), show = 0, z = -10)
1189
1190
1191    def destroy_and_init(self, parent, constant):
1192        for item in self.items():
1193            if hasattr(item, "attribute"):
1194                item.attribute = None
1195            self.removeItem(item)
1196
1198        self.footerCanvas.destroy_and_init(self,parent)
1199        self.initVars(parent, constant)
1200
1202        self.attributes.append(attr)
1203        if attr.minValue < self.minBeta:
1204            self.minBeta = attr.minValue
1205        if attr.maxValue > self.maxBeta:
1206            self.maxBeta = attr.maxValue
1207        if attr.maxValue-attr.minValue > self.max_difference:
1208            self.max_difference = attr.maxValue-attr.minValue
1209
1210    def hideAllMarkers(self):
1211        for at in self.attributes:
1212            at.marker.hide()
1213        self.footerCanvas.footer.marker.hide()
1214        self.footerCanvas.footerPercent.marker.hide()
1215        self.footerCanvas.connectedLine.hide()
1216        self.footerCanvas.hideCI()
1217        self.update()
1218        self.footerCanvas.update()
1219
1220    def showAllMarkers(self):
1221        for at in self.attributes:
1222            at.marker.show()
1223        self.footerCanvas.footer.marker.show()
1224        self.footerCanvas.footerPercent.marker.show()
1225        if self.parent.confidence_check:
1226            self.footerCanvas.showCI()
1227        if self.footerCanvas.footer.marker.x() == self.footerCanvas.footerPercent.marker.x():
1228            self.footerCanvas.connectedLine.setLine(self.footerCanvas.footer.marker.x(), self.footerCanvas.footer.marker.y(), self.footerCanvas.footerPercent.marker.x(), self.footerCanvas.footerPercent.marker.y())
1229            self.footerCanvas.connectedLine.show()
1230        self.update()
1231        self.footerCanvas.update()
1232
1233    def paint(self, rect, mapper):
1235#        if self.parent.showBaseLine:
1236        self.zeroLine.show()
1237#        else:
1238#            self.zeroLine.hide()
1239        curr_rect = QRect(rect.left(), rect.top(), rect.width(), 0)
1240        disc = False
1241
1242        for at in self.attributes:
1243            if (isinstance(at, AttrLineCont) or isinstance(at, AttrLineOrdered)) and self.parent.contType == 1:
1244                if disc:
1245                    curr_rect = QRect(rect.left(), curr_rect.bottom()+20, rect.width(), at.getHeight(self))
1246                    disc=False
1247                else:
1248                    curr_rect = QRect(rect.left(), curr_rect.bottom(), rect.width(), at.getHeight(self))
1249                at.paint2d(self, curr_rect, mapper)
1250            else:
1251                disc = True
1252                curr_rect = QRect(rect.left(), curr_rect.bottom(), rect.width(), at.getHeight(self))
1253                at.paint(self, curr_rect, mapper)
1254                # if histograms are used, a larger rect is required
1255                if self.parent.histogram:
1256                    curr_rect.setHeight(at.getHeight(self)+self.parent.histogram_size)
1257
1258
1259    def setSizes(self, parent):
1260        def getBottom():
1261            bottom, lastAt = 0, None
1262            for at in self.attributes:
1263                if lastAt and self.parent.contType == 1 and isinstance(at, AttrLineCont) and not isinstance(lastAt, AttrLineCont):
1264                    bottom += 20
1265                if (isinstance(at, AttrLineCont) or isinstance(at, AttrLineOrdered)) and self.parent.contType == 1:
1266                    bottom += at.getHeight(self)
1267                else:
1268                    bottom += at.getHeight(self)
1269                    if self.parent.histogram:
1270                        bottom += self.parent.histogram_size
1271                lastAt = at
1272            return bottom
1273
1274        self.pleft, self.pright, self.ptop, self.pbottom = 0, parent.graph.width() - 20, 0, self.parent.verticalSpacing
1275        self.pbottom += getBottom()
1276
1277        #graph sizes
1278        self.gleft = 0
1279        for at in self.attributes:
1280            if not (self.parent.contType == 1 and isinstance(at, AttrLineCont)) and max([l.boundingRect().width() for l in at.label])>self.gleft:
1281                self.gleft = max([l.boundingRect().width() for l in at.label])
1282        t = OWQCanvasFuncts.OWCanvasText(self.footerCanvas, self.footerCanvas.footerPercentName, show = 0)
1283        if t.boundingRect().width()>self.gleft:
1284            self.gleft = t.boundingRect().width()
1285
1286        #self.gleft = max(self.gleft, 100) # should really test footer width, and with of other lables
1287        self.gleft = max(self.gleft, 80)
1288        self.gleft +=20
1289        self.gright=self.pright-(self.pright-self.pleft)/10
1290        self.gtop = self.ptop + 10
1291        self.gbottom = self.pbottom - 10
1292
1293        self.gwidth = self.gright-self.gleft
1294        self.gheight = self.gbottom - self.gtop
1295
1296        if self.pbottom < parent.graph.height() - 30:
1297            self.pbottom = parent.graph.height() - 30
1298
1299
1300    def show(self):
1301        for item in self.items():
1302            item.hide()
1303
1305        self.footerCanvas.destroy_and_init(self,self.parent)
1306
1307        self.setSizes(self.parent)
1308        self.setBackgroundBrush(QBrush(QColor(Qt.white)))
1309        #self.resize(self.pright, self.pbottom)
1310
1311        curr_point = self.parent.verticalSpacing
1312        if self.parent.alignType == 0:
1313            if self.parent.yAxis == 0:
1314                self.mapper = Mapper_Linear_Left(self.max_difference,  self.gleft, self.gright)
1315            else:
1316                self.mapper = Mapper_Linear_Left(self.max_difference,  self.gleft, self.gright, maxLinearValue = self.max_difference)
1317        else:
1318            if self.parent.yAxis == 0:
1319                self.mapper = Mapper_Linear_Center(self.minBeta, self.maxBeta, self.gleft, self.gright)
1320            else:
1321                self.mapper = Mapper_Linear_Center(self.minBeta, self.maxBeta, self.gleft, self.gright, maxLinearValue = self.maxBeta, minLinearValue = self.minBeta)
1322        # draw HEADER and vertical line
1323        topRect=QRect(self.gleft, self.gtop, self.gwidth, 40)
1325        # draw nomogram
1326        middleRect=QRect(self.gleft, self.ptop, self.gwidth, self.gheight)
1327        self.paint(middleRect, self.mapper)
1328        # draw final line
1329        bottomRect=QRect(self.gleft, self.gtop, self.gwidth, 90)
1330        self.footerCanvas.paintFooter(bottomRect, self.parent.alignType, self.parent.yAxis, self.mapper)
1331        self.footerCanvas.updateMarkers()
1332        if self.parent.probability:
1333            self.showAllMarkers()
1334        self.update()
1335
1336    def showBaseLine(self, show):
1337        if show:
1338            self.zeroLine.show()
1339        else:
1340            self.zeroLine.hide()
1341        self.update()
1342
1343    def printOUT(self):
1344        print "Constant:", str(self.constant.betaValue)
1345        for a in self.attributes:
1346            print a.toString()
1347
1348    def findAttribute(self, y):
1349        for at in self.attributes:
1350            if y>at.minCanvasY and y<at.maxCanvasY:
1351                return at
1352        return None
1353
1354    def updateValues(self, x, y, obj):
1355        if obj.descriptor.attribute.updateValueXY(x, y):
1356            self.footerCanvas.updateMarkers()
1357            self.update()
1361
1362    def stopDragging(self):
1365
1366    def size(self):
1367        return self.sceneRect()
1368
1369
1370# ####################################################################
1371# CANVAS VIEWERS
1372# ####################################################################
1374    def __init__(self, canvas, mainArea):
1375        QGraphicsView.__init__(self,canvas, mainArea)
1376        self.setMouseTracking(True)
1377        self.viewport().setMouseTracking(True)
1378        self.setFocusPolicy(Qt.WheelFocus)
1379        self.mouseOverObject = None
1380
1381    def resizeEvent(self, event):
1382        QGraphicsView.resizeEvent(self,event)
1383        self.setSceneRect(0, 0, self.width(), self.height())
1384
1385    # ###################################################################
1386    # mouse is running around, perhaps Jerry is nearby ##################
1387    # or technically: user moved mouse ##################################
1388    def mouseMoveEvent(self, ev):
1389        if self.scene():
1390            items = filter(lambda ci: ci.zValue()==50, self.scene().items(QPointF(ev.pos())))
1391            if len(items)>0:
1392                if self.mouseOverObject:
1393                    self.mouseOverObject.hideSelected()
1394                self.mouseOverObject = items[0]
1395                self.mouseOverObject.showSelected()
1396                self.scene().update()
1397            elif self.mouseOverObject:
1398                self.mouseOverObject.hideSelected()
1399                self.mouseOverObject = None
1400                self.scene().update()
1401
1402
1403class OWNomogramGraph(QGraphicsView):
1404    def __init__(self, canvas, mainArea):
1405        QGraphicsView.__init__(self,canvas,mainArea)
1406        self.selectedObject = None
1407        self.mouseOverObject = None
1408        self.setMouseTracking(True)
1409        self.viewport().setMouseTracking(True)
1410        self.setFocusPolicy(Qt.WheelFocus)
1411        self.bDragging = False
1412        self.resizing = False
1413
1414    def resizeEvent(self, event):
1415        QGraphicsView.resizeEvent(self,event)
1416        if self.scene():
1417            self.resizing = True
1418            self.scene().show()
1419            self.setSceneRect(0, 0, self.width(), self.scene().height())
1420
1421    # ###################################################################
1422    # mouse button was pressed #########################################
1423    def mousePressEvent(self, ev):
1424        sc_pos = self.mapToScene(ev.pos())
1425        if self.scene() and ev.button() == Qt.LeftButton:
1426            items = filter(lambda ci: ci.zValue()==50, self.scene().items(sc_pos))
1427            if len(items)>0:
1428                self.selectedObject = items[0]
1429                #self.canvas().updateValues(ev.x(), ev.y(), self.selectedObject)
1430                self.bDragging = True
1431
1432    # ###################################################################
1433    # mouse button was released #########################################
1434    def mouseReleaseEvent(self, ev):
1435 #       if self.resizing:
1436 #           self.resizing = False
1437 #           self.cavnas().show()
1438        if self.bDragging:
1439            self.bDragging = False
1440            self.scene().stopDragging()
1441
1442    # ###################################################################
1443    # mouse is running around, perhaps Jerry is nearby ##################
1444    # or technically: user moved mouse ##################################
1445    def mouseMoveEvent(self, ev):
1446        sc_pos = self.mapToScene(ev.pos())
1447        if self.bDragging:
1448            self.scene().updateValues(sc_pos.x(), sc_pos.y(), self.selectedObject)
1449        elif self.scene():
1450            items = filter(lambda ci: ci.zValue()==50, self.scene().items(sc_pos))
1451            if len(items)>0:
1452                if self.mouseOverObject:
1453                    self.mouseOverObject.hideSelected()
1454                self.mouseOverObject = items[0]
1455                self.mouseOverObject.showSelected()
1456                self.scene().update()
1457            elif self.mouseOverObject:
1458                self.mouseOverObject.hideSelected()
1459                self.mouseOverObject = None
1460                self.scene().update()
1461
1462    def sizeHint(self):
1463        return QSize(200,200)
1464
1465
1466# ###################################################################################################################
1467# ------------------------------------------------------------------------------------------------------------------
1468# MAPPERS
1469# ------------------------------------------------------------------------------------------------------------------
1470# ------------------------------------------------------------------------------------------------------------------
1471def createSetOfVisibleValues(min, max, dif):
1472    if dif == 0.0 or max == min:
1473        return [0.0]
1474    upper = max-min+1.8*dif
1477
1478    dSum = numpy.arange(0, upper, dif)
1479    dSum = map(lambda x:x+add, dSum)
1480    return dSum
1481
1482
1483class Mapper_Linear_Fixed:
1484    def __init__(self, minBeta, maxBeta, left, right, maxLinearValue = 100, minLinearValue = -100, inverse = False):
1485        self.inverse = inverse
1486        self.minBeta = minBeta
1487        self.maxBeta = maxBeta
1488        self.left = left
1489        self.right = right
1490        self.maxLinearValue = maxLinearValue
1491        self.minLinearValue = minLinearValue
1492
1493        # find largest absolute beta and set it to maxLinearValue
1494        self.maxValue = self.maxLinearValue
1495        self.minValue = self.minLinearValue
1496
1497        # set actual values on graph (with roundings, but not yet, later)
1498        self.minGraphValue = self.minValue
1499        self.maxGraphValue = self.maxValue
1500        self.minGraphBeta = self.minBeta
1501        self.maxGraphBeta = self.maxBeta
1502
1503    def __call__(self, attrLine, error_factor = 1.96):
1504        beta = []
1505        b_error = []
1506        max_mapped = self.left
1507        min_mapped = self.right
1508        for at in attrLine.attValues:
1509            k = self.propBeta(at.betaValue, attrLine)
1510            beta.append(self.left+k*(self.right-self.left))
1511            if self.left+k*(self.right-self.left)>max_mapped:
1512                max_mapped = self.left+k*(self.right-self.left)
1513            if self.left+k*(self.right-self.left)<min_mapped:
1514                min_mapped = self.left+k*(self.right-self.left)
1515            k1 = self.propBeta(at.betaValue-error_factor*at.error, attrLine)
1516            k2 = self.propBeta(at.betaValue+error_factor*at.error, attrLine)
1517            b_error.append([self.left+k1*(self.right-self.left), self.left+k2*(self.right-self.left)])
1518        if max_mapped<min_mapped+5:
1519            max_mapped=min_mapped+5
1520        return (beta, b_error, min_mapped, max_mapped)
1521
1522    def mapBeta(self, betaVal, attrLine):
1523        k = self.propBeta(betaVal, attrLine)
1524        return self.left+k*(self.right-self.left)
1525
1526    def mapBetaToLinear(self, betaVal, attrLine):
1527        k = self.propBeta(betaVal, attrLine)
1528        return self.minGraphValue+k*(self.maxGraphValue-self.minGraphValue)
1529
1530    def getLeftMost(self):
1531        return self(self.minGraphBeta)
1532
1533    def getRightMost(self):
1534        return self(self.maxGraphBeta)
1535
1536    def getMinValue(self):
1537        return self.minValue
1538    def getMaxValue(self):
1539        return self.maxValue
1540
1541    # return proportional beta
1542    def propBeta(self, betaVal, attrLine):
1543        if self.inverse:
1544            return (self.maxGraphBeta-betaVal)/max((self.maxGraphBeta-self.minGraphBeta), aproxZero)
1545        else:
1546            return (betaVal-self.minGraphBeta)/max((self.maxGraphBeta-self.minGraphBeta), aproxZero)
1547
1548    # delay / offset that a mapper produces
1549    # in this case no aligning is uses, that is why delay is always 0
1550    def getDelay(self, nomogram):
1551        return 0
1552
1554        maxnum = rect.width()/(3*canvas.fontSize)
1555        if maxnum<1:
1556            maxnum=1
1557        d = (self.maxValue - self.minValue)/maxnum
1558        dif = getDiff(d)
1559
1560        dSum = createSetOfVisibleValues(self.minValue, self.maxValue, dif)
1561        if round(dSum[0],0) == dSum[0] and round(dSum[len(dSum)-1],0) == dSum[len(dSum)-1] and round(dif,0) == dif:
1562            conv = int
1563        else:
1564            conv = lambda x:x
1565
1566        # set new graph values
1567        k = (self.maxGraphBeta - self.minGraphBeta)/max((self.maxGraphValue - self.minGraphValue), aproxZero)
1568        self.maxGraphBeta = (dSum[len(dSum)-1]- self.minGraphValue)*k + self.minGraphBeta
1569        self.minGraphBeta = (dSum[0]- self.minGraphValue)*k + self.minGraphBeta
1570        self.minGraphValue = dSum[0]
1571        self.maxGraphValue = dSum[len(dSum)-1]
1572
1573        k = (self.maxGraphBeta-self.minGraphBeta)/max((self.maxGraphValue-self.minGraphValue), aproxZero)
1574        dSumValues = [(d,self.minGraphBeta + (d-self.minGraphValue)*k) for d in dSum]
1576        for d_i,d in enumerate(dSumValues):
1578            if d != dSumValues[-1]:
1579                val = AttValue(" "+str((d[0]+dSumValues[d_i+1][0])/2)+ " ", (d[1]+dSumValues[d_i+1][1])/2, markerWidth = 1)
1580                val.enable = False
1583
1584
1585class Mapper_Linear_Center:
1586    def __init__(self, minBeta, maxBeta, left, right, maxLinearValue = 100, minLinearValue = -100):
1587        if minBeta == 0:
1588            self.minBeta = aproxZero
1589        else:
1590            self.minBeta = minBeta
1591        if maxBeta == 0:
1592            self.maxBeta = aproxZero
1593        else:
1594            self.maxBeta = maxBeta
1595        self.left = left
1596        self.right = right
1597        self.maxLinearValue = maxLinearValue
1598        self.minLinearValue = minLinearValue
1599
1600        # find largest absolute beta and set it to maxLinearValue
1601        if abs(self.maxBeta) > abs(self.minBeta):
1602            self.maxValue = self.maxLinearValue
1603            self.minValue = self.maxLinearValue*self.minBeta/self.maxBeta
1604            if self.minValue < self.minLinearValue:
1605                self.minValue = self.minLinearValue
1606        else:
1607            self.minValue = self.minLinearValue
1608            self.maxValue = self.minLinearValue*self.maxBeta/self.minBeta
1609            if self.maxValue > self.maxLinearValue:
1610                self.maxValue = self.maxLinearValue
1611
1612        # set actual values on graph (with roundings, but not yet, later)
1613        self.minGraphValue = self.minValue
1614        self.maxGraphValue = self.maxValue
1615        self.minGraphBeta = self.minBeta
1616        self.maxGraphBeta = self.maxBeta
1617
1618    def __call__(self, attrLine, error_factor = 1.96):
1619        beta = []
1620        b_error = []
1621        max_mapped = self.left
1622        min_mapped = self.right
1623        for at in attrLine.attValues:
1624            k = self.propBeta(at.betaValue, attrLine)
1625            beta.append(self.left+k*(self.right-self.left))
1626            if self.left+k*(self.right-self.left)>max_mapped:
1627                max_mapped = self.left+k*(self.right-self.left)
1628            if self.left+k*(self.right-self.left)<min_mapped:
1629                min_mapped = self.left+k*(self.right-self.left)
1630            k1 = self.propBeta(at.betaValue-error_factor*at.error, attrLine)
1631            k2 = self.propBeta(at.betaValue+error_factor*at.error, attrLine)
1632            b_error.append([self.left+k1*(self.right-self.left), self.left+k2*(self.right-self.left)])
1633
1634        if max_mapped<min_mapped+5:
1635            max_mapped=min_mapped+5
1636        return (beta, b_error, min_mapped, max_mapped)
1637
1638    def mapBeta(self, betaVal, attrLine):
1639        k = self.propBeta(betaVal, attrLine)
1640        return self.left+k*(self.right-self.left)
1641
1642    def getLeftMost(self):
1643        return self(self.minGraphBeta)
1644
1645    def getRightMost(self):
1646        return self(self.maxGraphBeta)
1647
1648    def getMaxMapperValue(self):
1649        return self.maxGraphValue
1650    def getMinMapperValue(self):
1651        return self.minGraphValue
1652
1653    def getMaxValue(self, attr):
1654        if self.maxGraphBeta==0:
1655            return self.maxGraphValue*attr.maxValue/aproxZero
1656        return self.maxGraphValue*attr.maxValue/self.maxGraphBeta
1657
1658    def getMinValue(self, attr):
1659        if self.minGraphValue == 0:
1660            return self.minGraphValue*attr.minValue/aproxZero
1661        return self.minGraphValue*attr.minValue/self.minGraphBeta
1662
1663
1664
1665    # return proportional beta
1666    def propBeta(self, betaVal, attrLine):
1667        return (betaVal-self.minGraphBeta)/max((self.maxGraphBeta-self.minGraphBeta), aproxZero)
1668
1669    # delay / offset that a mapper produces
1670    # in this case no aligning is uses, that is why delay is always 0
1672        return 0
1673
1675        maxnum = rect.width()/(3*canvas.fontSize)
1676        if maxnum<1:
1677            maxnum=1
1678        d = (self.maxValue - self.minValue)/maxnum
1679        dif = getDiff(d)
1680        dSum = createSetOfVisibleValues(self.minValue, self.maxValue, dif);
1681
1682        if round(dSum[0],0) == dSum[0] and round(dSum[len(dSum)-1],0) == dSum[len(dSum)-1] and round(dif,0) == dif:
1683            conv = int
1684        else:
1685            conv = lambda x:x
1686
1687
1688        # set new graph values
1689        if self.minGraphValue == 0:
1690            self.minGraphBeta = self.minBeta
1691        else:
1692            self.minGraphBeta = self.minBeta*dSum[0]/self.minGraphValue
1693        if self.maxGraphValue == 0:
1694            self.maxGraphBeta = self.maxBeta
1695        else:
1696            self.maxGraphBeta = self.maxBeta*dSum[len(dSum)-1]/self.maxGraphValue
1697        self.minGraphValue = dSum[0]
1698        self.maxGraphValue = dSum[len(dSum)-1]
1699
1700        # coefficient to convert values into betas
1701        k = (self.maxGraphBeta-self.minGraphBeta)/max((self.maxGraphValue-self.minGraphValue), aproxZero)
1702
1704        for at in range(len(dSum)):
1706            if at != len(dSum)-1:
1707                val = AttValue(" "+str((dSum[at]+dSum[at+1])/2)+" ", self.minGraphBeta + ((dSum[at]+dSum[at+1])/2-self.minGraphValue)*k, markerWidth = 1)
1708                val.enable = False
1710
1712
1713
1714# it is very similar to Mapper_Linear_Center. It has the same methods, implementation is slightly different
1715class Mapper_Linear_Left:
1716    def __init__(self, max_difference, left, right, maxLinearValue = 100):
1717        self.max_difference = max_difference
1718        self.left = left
1719        self.right = right
1720        self.maxLinearValue = maxLinearValue
1721
1722    def __call__(self, attrLine, error_factor = 1.96):
1723        beta = []
1724        b_error = []
1725        minb = self.right
1726        maxb = self.left
1727        for at in attrLine.attValues:
1728            k = (at.betaValue-attrLine.minValue)/max(self.max_difference, aproxZero)
1729            beta.append(self.left + k*(self.right-self.left))
1730            if self.left + k*(self.right-self.left)>maxb:
1731                maxb = self.left + k*(self.right-self.left)
1732            if self.left + k*(self.right-self.left)<minb:
1733                minb = self.left + k*(self.right-self.left)
1734            k1 = (at.betaValue-error_factor*at.error-attrLine.minValue)/max(self.max_difference, aproxZero)
1735            k2 = (at.betaValue+error_factor*at.error-attrLine.minValue)/max(self.max_difference, aproxZero)
1736            b_error.append([self.left + k1*(self.right-self.left), self.left + k2*(self.right-self.left)])
1737
1738        if maxb<minb+5:
1739            maxb=minb+5
1740        return (beta, b_error, minb, maxb)
1741
1742    def mapBeta(self, betaVal, attrLine):
1743        k = (betaVal-attrLine.minValue)/max(self.max_difference, aproxZero)
1744        return self.left+k*(self.right-self.left)
1745
1746    def propBeta(self, betaVal, attrLine):
1747        return (betaVal-attrLine.minValue)/max(self.max_difference, aproxZero)
1748
1749    def getMaxMapperValue(self):
1750        return self.maxLinearValue
1751    def getMinMapperValue(self):
1752        return 0
1753    def getMaxValue(self, attr):
1754        return self.maxLinearValue*(attr.maxValue-attr.minValue)/max(self.max_difference, aproxZero)
1755    def getMinValue(self, attr):
1756        return 0
1757
1759        delay = 0
1760        for at in nomogram.attributes:
1761            delay += at.minValue
1762        return delay
1763
1765        maxnum = rect.width()/(3*canvas.fontSize)
1766        if maxnum<1:
1767            maxnum=1
1768        d = self.maxLinearValue/maxnum
1769        dif = max(getDiff(d),10e-6)
1770        dSum = []
1771        dSum = numpy.arange(0, self.maxLinearValue+dif, dif)
1772        if len(dSum)<1:
1773            dSum = numpy.arange(0, self.maxLinearValue+dif, dif/2.)
1774        dSum = map(lambda x:x, dSum)
1775        if round(dSum[0],0) == dSum[0] and round(dSum[len(dSum)-1],0) == dSum[len(dSum)-1] and round(dif,0) == dif:
1776            conv = int
1777        else:
1778            conv = lambda x:x
1779
1780        k = self.max_difference/(self.maxLinearValue or 1)
1781
1783        for at in range(len(dSum)):