From fe97bea449c04c54f29206ca1dc0e9ba5ec5dddb Mon Sep 17 00:00:00 2001 From: esitarski Date: Thu, 15 Aug 2024 09:15:58 -0400 Subject: [PATCH 1/3] More refinement to lap breakdown. --- LapsToGoCount.py | 100 +++++++++++++++++++++++++++++------------------ MainWin.py | 2 +- NumKeypad.py | 9 ++++- 3 files changed, 70 insertions(+), 41 deletions(-) diff --git a/LapsToGoCount.py b/LapsToGoCount.py index d5772f79..838356d7 100644 --- a/LapsToGoCount.py +++ b/LapsToGoCount.py @@ -135,7 +135,7 @@ def LapsToGoCount( t=None ): race = Model.race if not race or race.isUnstarted() or race.isFinished(): - return ltgc + return ltgc, sc if not t: t = race.curRaceTime() @@ -155,10 +155,11 @@ def LapsToGoCount( t=None ): except KeyError: continue - if rr.raceTimes[-1] <= tSearch or not (lap := bisect_right(rr.raceTimes, tSearch) ): + if not (lap := bisect_right(rr.raceTimes, tSearch) ): continue + lap -= 1 - lapsToGoCountCategory[len(rr.raceTimes) - lap] += 1 + lapsToGoCountCategory[len(rr.raceTimes) - lap - 1] += 1 ltgc[category] = sorted( lapsToGoCountCategory.items(), reverse=True ) lapsToGoCountCategory.clear() @@ -174,7 +175,8 @@ def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, super().__init__(parent, id, pos, size, style, validator, name) - self.barBrushes = [wx.Brush(wx.Colour( int(c[:2],16), int(c[2:4],16), int(c[4:],16)), wx.SOLID) for c in ('D8E6AD', 'E6ADD8', 'ADD8E6')] + #self.barBrushes = [wx.Brush(wx.Colour( int(c[:2],16), int(c[2:4],16), int(c[4:],16)), wx.SOLID) for c in ('D8E6AD', 'E6ADD8', 'ADD8E6')] + self.barBrushes = [wx.Brush(wx.Colour( int(c[:2],16), int(c[2:4],16), int(c[4:],16)), wx.SOLID) for c in ('A0A0A0', 'D3D3D3', 'E5E4E2')] self.statusKeys = sorted( (k for k in Model.Rider.statusSortSeq.keys() if isinstance(k, int)), key=lambda k: Model.Rider.statusSortSeq[k] ) self.SetBackgroundColour(wx.WHITE) @@ -231,94 +233,114 @@ def Draw( self, dc ): race = Model.race categories = race.getCategories() - yTop = xLeft = int( min( height * 0.03, width * 0.03 ) ) + catLabelFontHeight = min( 12, int(height * 0.1) ) + catLabelHeight = int( catLabelFontHeight * 2.5 ) + yTop = catLabelHeight + xLeft = 0 xRight = width - xLeft yBottom = height - yTop catHeight = int( (yBottom-yTop) / len(categories) ) - catLabelFontHeight = min( 12, int(catHeight * 0.1) ) - catLabelHeight = int( catLabelFontHeight * 2.5 ) catFieldHeight = catHeight - catLabelHeight + barFieldHeight = catFieldHeight - catLabelHeight catLabelFont = wx.Font( wx.FontInfo(catLabelFontHeight).FaceName('Helvetica') ) catLabelFontBold = wx.Font( wx.FontInfo(catLabelFontHeight).FaceName('Helvetica').Bold() ) catLabelMargin = int( (catLabelHeight - catLabelFontHeight) / 2 ) - def statusCountStr( sc ): + Finisher = Model.Rider.Finisher + def statusCountStr( sc, lap0Count ): statusNames = Model.Rider.statusNames translate = _ t = [] + onCourseCount = 0 for status in self.statusKeys: if count := sc.get(status, 0): + if status == Finisher: + onCourseCount = count - lap0Count sName = translate(statusNames[status].replace('Finisher','Competing')) t.append( f'{count}={sName}' ) - return ' | '.join( t ) + return f"{onCourseCount}={_('OnCourse')} | " + ' | '.join( t ) titleStyle = DCStyle( dc, Font=catLabelFontBold ) + chartLineStyle = DCStyle( dc, Pen=greyPen, Brush=wx.TRANSPARENT_BRUSH ) + lap0Total = finisherTotal = 0 yCur = yTop for cat in categories: - # Draw the lines. - dc.SetPen( greyPen ) - dc.DrawLine( xLeft, yCur + catFieldHeight, xLeft, yCur ) - dc.DrawLine( xRight, yCur + catFieldHeight, xRight, yCur ) - dc.DrawLine( xLeft, yCur + catFieldHeight, xRight, yCur + catFieldHeight ) - dc.DrawLine( xLeft, yCur, xRight, yCur ) + # Draw the chart lines. + with chartLineStyle: + dc.DrawRectangle( xLeft, yCur, xRight-xLeft, catFieldHeight ) # Draw the lap bars. ltg = lapsToGoCount[cat] barCount = 1 if ltg: barCount = ltg[0][0] - ltg[-1][0] + 1 - + + finisherTotal += statusCount[cat].get( Finisher, 0 ) + # Compute the barwidths so they take the entire horizontal line. barWidth = (xRight - xLeft) / barCount barX = [round( xLeft + i * barWidth ) for i in range(barCount)] barX.append( xRight ) + barTextWidth = barWidth - 2 # Draw the bars and labels. countTotal = sum( count for lap, count in ltg ) dc.SetPen( greyPen ) dc.SetFont( catLabelFont ) + lap0Count = 0 for lap, count in ltg: - barHeight = round( catFieldHeight * count / countTotal ) - i = ltg[0][0] - lap - dc.SetBrush( self.barBrushes[lap%len(self.barBrushes)] ) - dc.DrawRectangle( barX[i], yCur + catFieldHeight - barHeight, barX[i+1] - barX[i], barHeight ) - if lap: - s = f'{count} @ {lap} {_("to go")}' + s = f'{lap} {_("to go")}' tWidth = dc.GetTextExtent( s ).width - if tWidth >= barWidth - 2: - s = f'{count} @ {lap}' + if tWidth >= barTextWidth: + s = f'{lap}' tWidth = dc.GetTextExtent( s ).width - if tWidth >= barWidth - 2: - s = f'{count}@{lap}' - tWidth = dc.GetTextExtent( s ).width else: - s = f'{count} {_("Finished")}' + lap0Count = lap + lap0Total += lap + s = f'{_("Finished")}' tWidth = dc.GetTextExtent( s ).width - if tWidth >= barWidth - 2: - s = f'{count} @ {_("Fin")}' + if tWidth >= barTextWidth: + s = f'{_("Fin")}' tWidth = dc.GetTextExtent( s ).width - if tWidth >= barWidth - 2: - s = f'{count}@{_("Fin")}' - tWidth = dc.GetTextExtent( s ).width + + barHeight = round( barFieldHeight * count / countTotal ) + if barHeight < catLabelFontHeight: + continue + + i = ltg[0][0] - lap + dc.SetBrush( self.barBrushes[lap%len(self.barBrushes)] ) + dc.DrawRectangle( barX[i], yCur + catFieldHeight - barHeight, barX[i+1] - barX[i], barHeight ) y = yCur + catHeight - catLabelMargin - catLabelFontHeight x = barX[i] + (barX[i+1] - barX[i] - tWidth) // 2 dc.DrawText( s, x, y ) + + s = f'{count}' + tWidth = dc.GetTextExtent( s ).width + y = min( yCur + catFieldHeight - catLabelFontHeight- catLabelFontHeight//4, yCur + catFieldHeight - barHeight + catLabelFontHeight//2 ) + x = barX[i] + (barX[i+1] - barX[i] - tWidth) // 2 + dc.DrawText( s, x, y ) - # Draw the category label with the on course total. - #onCourse = countTotal - (ltg[-1][1] if ltg and ltg[-1][0] == 0 else 0) - #finished = countTotal - onCourse + # Draw the category label with the status totals. with titleStyle: - dc.DrawText( f'{cat.fullname}', xLeft + catLabelMargin, yCur + catLabelMargin ) - dc.DrawText( f'{statusCountStr(statusCount[cat])}', xLeft + catLabelMargin*4, int(yCur + catLabelMargin + catLabelFontHeight*1.75) ) + catText = f'{cat.fullname}' + titleTextWidth = dc.GetTextExtent(catText).width + dc.DrawText( catText, xLeft + catLabelMargin, yCur + catLabelMargin ) + dc.DrawText( f'{statusCountStr(statusCount[cat], lap0Count)}', xLeft + titleTextWidth + catLabelMargin*2, int(yCur + catLabelMargin) ) + + yCur += catHeight - yCur += catHeight + with titleStyle: + catText = f'{_("All")}' + titleTextWidth = dc.GetTextExtent(catText).width + dc.DrawText( catText, xLeft + catLabelMargin, catLabelFontHeight//2 ) + dc.DrawText( f'{finisherTotal-lap0Total}={_("OnCourse")}', xLeft + titleTextWidth + catLabelMargin*2, catLabelFontHeight//2 ) def OnEraseBackground(self, event): # This is intentionally empty. diff --git a/MainWin.py b/MainWin.py index 7425526e..1401e8a5 100644 --- a/MainWin.py +++ b/MainWin.py @@ -2686,7 +2686,7 @@ def onCloseWindow( self, event ): self.doCleanup() wx.Exit() - @logCall + #@logCall def writeRace( self, doCommit = True ): if doCommit: self.commit() diff --git a/NumKeypad.py b/NumKeypad.py index fb7b769c..21927765 100644 --- a/NumKeypad.py +++ b/NumKeypad.py @@ -20,6 +20,7 @@ from NonBusyCall import NonBusyCall from SetLaps import SetLaps from InputUtils import enterCodes, validKeyCodes, clearCodes, actionCodes, getRiderNumsFromText, MakeKeypadButton +from LapsToGoCount import LapsToGoCountGraph SplitterMinPos = 390 SplitterMaxPos = 530 @@ -419,6 +420,7 @@ def __init__( self, parent, id = wx.ID_ANY ): #------------------------------------------------------------------------------ # Rider Lap Count. rcVertical = wx.BoxSizer( wx.VERTICAL ) + ''' rcVertical.AddSpacer( 32 ) self.categoryStatsList = wx.ListCtrl( panel, wx.ID_ANY, style = wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_HRULES|wx.BORDER_NONE ) @@ -427,7 +429,9 @@ def __init__( self, parent, id = wx.ID_ANY ): self.categoryStatsList.AppendColumn( _('Composition'), wx.LIST_FORMAT_LEFT, 130 ) self.categoryStatsList.SetColumnWidth( 0, wx.LIST_AUTOSIZE_USEHEADER ) self.categoryStatsList.SetColumnWidth( 1, wx.LIST_AUTOSIZE_USEHEADER ) - rcVertical.Add( self.categoryStatsList, 1, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.BOTTOM, border = 4 ) + ''' + self.categoryStatsList = LapsToGoCountGraph( panel ) + rcVertical.Add( self.categoryStatsList, 1, flag=wx.EXPAND|wx.TOP|wx.RIGHT, border = 4 ) horizontalMainSizer.Add( rcVertical, 1, flag=wx.EXPAND|wx.LEFT, border = 4 ) self.horizontalMainSizer = horizontalMainSizer @@ -663,6 +667,8 @@ def refreshLaps( self ): wx.CallAfter( self.refreshRaceHUD ) def refreshRiderCategoryStatsList( self ): + self.categoryStatsList.Refresh() + ''' self.categoryStatsList.DeleteAllItems() race = Model.race if not race: @@ -693,6 +699,7 @@ def appendListRow( row = tuple(), colour = None, bold = None ): self.categoryStatsList.SetColumnWidth( 0, wx.LIST_AUTOSIZE_USEHEADER ) self.categoryStatsList.SetColumnWidth( 1, wx.LIST_AUTOSIZE_USEHEADER ) + ''' def refreshLastRiderOnCourse( self ): race = Model.race From 72f964197326e73428a1fa3186985801434a4c25 Mon Sep 17 00:00:00 2001 From: esitarski Date: Thu, 15 Aug 2024 12:02:26 -0400 Subject: [PATCH 2/3] Fixed more display issues in LapCountDisplay. --- LapsToGoCount.py | 40 ++++++++++++++++++++++------------------ NumKeypad.py | 48 +++--------------------------------------------- Version.py | 2 +- 3 files changed, 26 insertions(+), 64 deletions(-) diff --git a/LapsToGoCount.py b/LapsToGoCount.py index 838356d7..d927d8a2 100644 --- a/LapsToGoCount.py +++ b/LapsToGoCount.py @@ -129,16 +129,17 @@ def __exit__(self, *args): else: getattr( self.dc, funcName )( vNew ) +@Model.memoize def LapsToGoCount( t=None ): ltgc = {} # dict indexed by category with a list of (lapsToGo, count). sc = {} # dict index by category with counts of each status. race = Model.race - if not race or race.isUnstarted() or race.isFinished(): + if not race or race.isUnstarted(): return ltgc, sc if not t: - t = race.curRaceTime() + t = race.curRaceTime() if race.isRunning() else float('inf') Finisher = Model.Rider.Finisher lapsToGoCountCategory = defaultdict( int ) @@ -175,7 +176,6 @@ def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, super().__init__(parent, id, pos, size, style, validator, name) - #self.barBrushes = [wx.Brush(wx.Colour( int(c[:2],16), int(c[2:4],16), int(c[4:],16)), wx.SOLID) for c in ('D8E6AD', 'E6ADD8', 'ADD8E6')] self.barBrushes = [wx.Brush(wx.Colour( int(c[:2],16), int(c[2:4],16), int(c[4:],16)), wx.SOLID) for c in ('A0A0A0', 'D3D3D3', 'E5E4E2')] self.statusKeys = sorted( (k for k in Model.Rider.statusSortSeq.keys() if isinstance(k, int)), key=lambda k: Model.Rider.statusSortSeq[k] ) @@ -239,7 +239,7 @@ def Draw( self, dc ): xLeft = 0 xRight = width - xLeft - yBottom = height - yTop + yBottom = height catHeight = int( (yBottom-yTop) / len(categories) ) @@ -262,7 +262,7 @@ def statusCountStr( sc, lap0Count ): onCourseCount = count - lap0Count sName = translate(statusNames[status].replace('Finisher','Competing')) t.append( f'{count}={sName}' ) - return f"{onCourseCount}={_('OnCourse')} | " + ' | '.join( t ) + return f"{onCourseCount}={_('OnCourse')}" + (" | " if t else '') + ' | '.join( t ) titleStyle = DCStyle( dc, Font=catLabelFontBold ) chartLineStyle = DCStyle( dc, Pen=greyPen, Brush=wx.TRANSPARENT_BRUSH ) @@ -270,9 +270,10 @@ def statusCountStr( sc, lap0Count ): lap0Total = finisherTotal = 0 yCur = yTop for cat in categories: - # Draw the chart lines. - with chartLineStyle: - dc.DrawRectangle( xLeft, yCur, xRight-xLeft, catFieldHeight ) + if barFieldHeight >= catLabelFontHeight: + # Draw the chart lines. + with chartLineStyle: + dc.DrawRectangle( xLeft, yCur, xRight-xLeft, catFieldHeight ) # Draw the lap bars. ltg = lapsToGoCount[cat] @@ -301,31 +302,34 @@ def statusCountStr( sc, lap0Count ): s = f'{lap}' tWidth = dc.GetTextExtent( s ).width else: - lap0Count = lap - lap0Total += lap + lap0Count = count + lap0Total += count s = f'{_("Finished")}' tWidth = dc.GetTextExtent( s ).width if tWidth >= barTextWidth: s = f'{_("Fin")}' tWidth = dc.GetTextExtent( s ).width - barHeight = round( barFieldHeight * count / countTotal ) - if barHeight < catLabelFontHeight: + if barFieldHeight < catLabelFontHeight: continue + + barHeight = round( barFieldHeight * count / countTotal ) i = ltg[0][0] - lap dc.SetBrush( self.barBrushes[lap%len(self.barBrushes)] ) dc.DrawRectangle( barX[i], yCur + catFieldHeight - barHeight, barX[i+1] - barX[i], barHeight ) - y = yCur + catHeight - catLabelMargin - catLabelFontHeight - x = barX[i] + (barX[i+1] - barX[i] - tWidth) // 2 - dc.DrawText( s, x, y ) + if tWidth < barTextWidth: + y = yCur + catHeight - catLabelMargin - catLabelFontHeight + x = barX[i] + (barX[i+1] - barX[i] - tWidth) // 2 + dc.DrawText( s, x, y ) s = f'{count}' tWidth = dc.GetTextExtent( s ).width - y = min( yCur + catFieldHeight - catLabelFontHeight- catLabelFontHeight//4, yCur + catFieldHeight - barHeight + catLabelFontHeight//2 ) - x = barX[i] + (barX[i+1] - barX[i] - tWidth) // 2 - dc.DrawText( s, x, y ) + if tWidth < barTextWidth: + y = min( yCur + catFieldHeight - catLabelFontHeight- catLabelFontHeight//4, yCur + catFieldHeight - barHeight + catLabelFontHeight//2 ) + x = barX[i] + (barX[i+1] - barX[i] - tWidth) // 2 + dc.DrawText( s, x, y ) # Draw the category label with the status totals. with titleStyle: diff --git a/NumKeypad.py b/NumKeypad.py index 21927765..21d5724b 100644 --- a/NumKeypad.py +++ b/NumKeypad.py @@ -420,18 +420,8 @@ def __init__( self, parent, id = wx.ID_ANY ): #------------------------------------------------------------------------------ # Rider Lap Count. rcVertical = wx.BoxSizer( wx.VERTICAL ) - ''' - rcVertical.AddSpacer( 32 ) - - self.categoryStatsList = wx.ListCtrl( panel, wx.ID_ANY, style = wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_HRULES|wx.BORDER_NONE ) - self.categoryStatsList.SetFont( wx.Font(int(fontSize*0.9), wx.DEFAULT, wx.NORMAL, wx.NORMAL) ) - self.categoryStatsList.AppendColumn( _('Category'), wx.LIST_FORMAT_LEFT, 140 ) - self.categoryStatsList.AppendColumn( _('Composition'), wx.LIST_FORMAT_LEFT, 130 ) - self.categoryStatsList.SetColumnWidth( 0, wx.LIST_AUTOSIZE_USEHEADER ) - self.categoryStatsList.SetColumnWidth( 1, wx.LIST_AUTOSIZE_USEHEADER ) - ''' - self.categoryStatsList = LapsToGoCountGraph( panel ) - rcVertical.Add( self.categoryStatsList, 1, flag=wx.EXPAND|wx.TOP|wx.RIGHT, border = 4 ) + self.lapsToGoCountGraph = LapsToGoCountGraph( panel ) + rcVertical.Add( self.lapsToGoCountGraph, 1, flag=wx.EXPAND|wx.TOP|wx.RIGHT, border = 4 ) horizontalMainSizer.Add( rcVertical, 1, flag=wx.EXPAND|wx.LEFT, border = 4 ) self.horizontalMainSizer = horizontalMainSizer @@ -667,39 +657,7 @@ def refreshLaps( self ): wx.CallAfter( self.refreshRaceHUD ) def refreshRiderCategoryStatsList( self ): - self.categoryStatsList.Refresh() - ''' - self.categoryStatsList.DeleteAllItems() - race = Model.race - if not race: - return - - def appendListRow( row = tuple(), colour = None, bold = None ): - r = self.categoryStatsList.InsertItem( self.categoryStatsList.GetItemCount(), '{}'.format(row[0]) if row else '' ) - for c in range(1, len(row)): - self.categoryStatsList.SetItem( r, c, '{}'.format(row[c]) ) - if colour is not None: - item = self.categoryStatsList.GetItem( r ) - item.SetTextColour( colour ) - self.categoryStatsList.SetItem( item ) - if bold is not None: - item = self.categoryStatsList.GetItem( r ) - font = self.categoryStatsList.GetFont() - font.SetWeight( wx.FONTWEIGHT_BOLD ) - item.SetFont( font ) - self.categoryStatsList.SetItem( item ) - return r - - for catStat in getCategoryStats(): - if catStat[0] == _('All'): - colour, bold = wx.BLUE, None - else: - colour = bold = None - appendListRow( catStat, colour, bold ) - - self.categoryStatsList.SetColumnWidth( 0, wx.LIST_AUTOSIZE_USEHEADER ) - self.categoryStatsList.SetColumnWidth( 1, wx.LIST_AUTOSIZE_USEHEADER ) - ''' + self.lapsToGoCountGraph.Refresh() def refreshLastRiderOnCourse( self ): race = Model.race diff --git a/Version.py b/Version.py index 4f4be93f..92a277a4 100644 --- a/Version.py +++ b/Version.py @@ -1 +1 @@ -AppVerName="CrossMgr 3.1.61-private" +AppVerName="CrossMgr 3.1.62-private" From 4e52e6d3bbd7c0a396f60ac3c9853de3a330b24c Mon Sep 17 00:00:00 2001 From: esitarski Date: Fri, 16 Aug 2024 11:49:11 -0400 Subject: [PATCH 3/3] Fixed display bugs in LapsToGoCount with TimeTrials --- LapsToGoCount.py | 25 ++++++++++++++++++++++--- MainWin.py | 3 +++ Utils.py | 4 +++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/LapsToGoCount.py b/LapsToGoCount.py index d927d8a2..7cb98643 100644 --- a/LapsToGoCount.py +++ b/LapsToGoCount.py @@ -137,18 +137,37 @@ def LapsToGoCount( t=None ): race = Model.race if not race or race.isUnstarted(): return ltgc, sc + isTimeTrial = race.isTimeTrial if not t: t = race.curRaceTime() if race.isRunning() else float('inf') Finisher = Model.Rider.Finisher + NP = Model.Rider.NP + lapsToGoCountCategory = defaultdict( int ) for category in race.getCategories(): - statusCategory = defaultdict( int ) + categoryLaps = category.getNumLaps() + statusCategory = defaultdict( int ) for rr in GetResults(category): statusCategory[rr.status] += 1 - if rr.status != Finisher or not rr.raceTimes: + + if rr.status != Finisher: + if isTimeTrial and rr.status == NP and categoryLaps is not None: + # Record TT riders who have started with the full laps. + rider = race.riders[rr.num] + # print( rider.num, rider.firstTime, t, rr.status, categoryLaps ) + if rider.firstTime is not None and t >= rider.firstTime: + lapsToGoCountCategory[categoryLaps] += 1 + # Reclassify NP to Finisher as we know the rider is on course. + statusCategory[NP] -= 1 + statusCategory[Finisher] += 1 + continue + + if not rr.raceTimes: + if isTimeTrial and categoryLaps is not None: + lapsToGoCountCategory[categoryLaps] += 1 continue try: @@ -201,7 +220,7 @@ def SetBackgroundColour(self, colour): def ShouldInheritColours(self): return True - def OnPaint(self, event ): + def OnPaint(self, event): dc = wx.PaintDC(self) self.Draw(dc) diff --git a/MainWin.py b/MainWin.py index 1401e8a5..9edbd278 100644 --- a/MainWin.py +++ b/MainWin.py @@ -4065,6 +4065,9 @@ def refresh( self ): self.updateRaceClock() def refreshTTStart( self ): + if Model.race: + # If a rider started the TT, force the results to be re-computed if necessary. + Model.race.setChanged() if self.notebook.GetSelection() in (self.iHistoryPage, self.iRecordPage): self.refreshCurrentPage() diff --git a/Utils.py b/Utils.py index 34490428..d8600991 100644 --- a/Utils.py +++ b/Utils.py @@ -629,8 +629,10 @@ def refresh(): mainWin.refresh() def refreshForecastHistory(): - if mainWin is not None: + try: mainWin.forecastHistory.refresh() + except AttributeError: + pass def updateUndoStatus(): if mainWin is not None: