소스 검색

add graph to price check app

Daniel Sheffield 2 년 전
부모
커밋
f663825319
2개의 변경된 파일90개의 추가작업 그리고 40개의 파일을 삭제
  1. 56 7
      app/activities/PriceCheck.py
  2. 34 33
      app/activities/TransactionEditor.py

+ 56 - 7
app/activities/PriceCheck.py

@@ -28,7 +28,8 @@ from ..widgets import (
     AutoCompleteFloatEdit,
     FocusWidget,
     AutoCompletePopUp,
-    NoTabCheckBox
+    NoTabCheckBox,
+    FlowBarGraphWithVScale,
 )
 from ..db_utils import QueryManager
 from . import ActivityManager, show_or_exit
@@ -91,13 +92,47 @@ class PriceCheck(FocusWidget):
             cb.set_state('mixed')
         for (_, tf) in self.text_fields.items():
             tf.set_text('')
-        self.update()
-        return self
+        self.graph.set_data([],0)
+        return self.update()
 
     def update(self):
         self.update_historic_prices(self.data)
         return self
 
+    def update_graph(self, df):
+        # after truncating, need to recalculate avg(median), min, max
+        df = df.sort_values(
+            'ts_raw', ascending=True, ignore_index=True
+        ).truncate(
+            before=max(0, len(df.index)-self.graph._canvas_width)
+        )
+        data = df[['$/unit','quantity']].apply(
+            lambda x: (float(x['$/unit']), float(x['quantity'])),
+            axis=1, result_type='broadcast'
+        )
+        data['avg'] = (data['$/unit']*data['quantity']).sum()/data['quantity'].sum()
+        data_max = data.max()['$/unit'] #.max()
+        assert len(data['avg'].unique()) == 1
+        norm = [ (x,) for x in data['$/unit'] ] #.to_records(index=False)
+        self.graph.set_data(norm, data_max,
+            vscale=[x for x in map(float, [
+                data['$/unit'].min(),
+                data['$/unit'].median(),
+                data['avg'].iloc[0],
+                data_max
+            ])]
+        )
+        #self.graph.set_bar_width(1)
+        # canvas_width = 10 + pad + pad + 10
+        date_strlen = (self.graph.canvas_width - 20)
+        ex = "─" if date_strlen % 2 else ""
+        plen = date_strlen//2
+        caption = f"{df['ts_raw'].min():%d/%m/%Y}"
+        caption += f"{{p:>{plen}}}{ex}{{p:<{plen}}}".format(
+            p="─")
+        caption += f"{df['ts_raw'].max():%d/%m/%Y}"
+        self.graph.set_caption(caption)
+
     def update_historic_prices(self, data):
         organic = None if data['organic'] == 'mixed' else data['organic']
         sort = '$/unit' if self.buttons['sort_price'].state else 'ts'
@@ -133,6 +168,7 @@ class PriceCheck(FocusWidget):
         self.text_fields['dbview'].set_text(
             self.rating.get_historic_prices(df)
         )
+        self.update_graph(df)
 
     def __init__(self,
         activity_manager: ActivityManager,
@@ -180,7 +216,7 @@ class PriceCheck(FocusWidget):
             'quantity',
             'price',
         ]
-        bottom_pane = [ 'dbview', ]
+        bottom_pane = [ 'graph', 'dbview', ]
 
         self.query_manager = query_manager
         self.organic_checkbox = self.checkboxes['organic']
@@ -194,7 +230,11 @@ class PriceCheck(FocusWidget):
 
         connect_signal(self.buttons['clear'], 'click', lambda x: self.clear().update())
         connect_signal(self.buttons['exit'], 'click', lambda x: show_or_exit('esc'))
-
+        self.graph = FlowBarGraphWithVScale(
+            50, 14,
+            ['bg','popup_focus', 'badge_neutral' ],
+            hatt=['dark red', 'dark red', 'dark red']
+        )
 
         self.clear()
 
@@ -222,6 +262,7 @@ class PriceCheck(FocusWidget):
                 ), 'streak'), title=k.title(), title_align='left')
             ) for k in self.edit_fields
         ])
+
         _widgets.update({
             'dbview': LineBox(
                 AttrMap(self.text_fields['dbview'], 'streak'),
@@ -259,8 +300,16 @@ class PriceCheck(FocusWidget):
             lambda x: _widgets[x] if x is not None else Divider,
             badge
           )),
-          'bottom_pane': _widgets['dbview'],
         }
+        _widgets.update({
+            'graph': LineBox(
+                self.graph,
+                title="Historic Price", title_align='left'
+              ),
+        })
+        components.update({
+          'bottom_pane': [ _widgets['graph'], _widgets['dbview'] ],
+        })
         components.update({
           'left_pane': Pile([
             components['left_pane'],
@@ -277,7 +326,7 @@ class PriceCheck(FocusWidget):
             Columns((components['left_pane'], components['right_pane']),
                 dividechars=0,
             ),
-            components['bottom_pane'],
+            Pile(components['bottom_pane']),
         ])
         widget = Filler(widget, 'top')
         widget = AttrMap(widget, 'bg')

+ 34 - 33
app/activities/TransactionEditor.py

@@ -114,38 +114,8 @@ class TransactionEditor(FocusWidget):
         )
         self.update_historic_prices(data)
         return self
-
-    def update_historic_prices(self, data):
-        organic = None if data['organic'] == 'mixed' else data['organic']
-        #sort = '$/unit' if self.buttons['sort_price'].state else 'ts'
-        product, unit = data['product'] or None, data['unit'] or None
-        try:
-            price = Decimal(data['price'])
-        except InvalidOperation:
-            price = None
-
-        try:
-            quantity = Decimal(data['quantity'])
-        except InvalidOperation:
-            quantity = None
-
-        if None in (product, unit):
-            self.rating.update_rating(None, None, None, unit)
-            return
-
-        df = self.query_manager.get_historic_prices_data(unit, product=product, organic=organic, sort='ts')
-        if df.empty:
-            self.rating.update_rating(None, None, None, unit)
-            return
-
-        assert len(df['avg'].unique()) == 1, f"There should be only one average price: {df['avg'].unique()}"
-
-        # all time (or all data) avg(mean), min, max
-        _avg, _min, _max = [
-          float(x) for x in df[['avg','min','max']].iloc[0]
-        ]
-        self.rating.update_rating(_avg, _min, _max, unit, price=price, quantity=quantity)
-
+    
+    def update_graph(self, df):
         # after truncating, need to recalculate avg(median), min, max
         df = df.sort_values(
             'ts_raw', ascending=True, ignore_index=True
@@ -179,6 +149,37 @@ class TransactionEditor(FocusWidget):
         caption += f"{df['ts_raw'].max():%d/%m/%Y}"
         self.graph.set_caption(caption)
 
+    def update_historic_prices(self, data):
+        organic = None if data['organic'] == 'mixed' else data['organic']
+        #sort = '$/unit' if self.buttons['sort_price'].state else 'ts'
+        product, unit = data['product'] or None, data['unit'] or None
+        try:
+            price = Decimal(data['price'])
+        except InvalidOperation:
+            price = None
+
+        try:
+            quantity = Decimal(data['quantity'])
+        except InvalidOperation:
+            quantity = None
+
+        if None in (product, unit):
+            self.rating.update_rating(None, None, None, unit)
+            return
+
+        df = self.query_manager.get_historic_prices_data(unit, product=product, organic=organic, sort='ts')
+        if df.empty:
+            self.rating.update_rating(None, None, None, unit)
+            return
+
+        assert len(df['avg'].unique()) == 1, f"There should be only one average price: {df['avg'].unique()}"
+
+        # all time (or all data) avg(mean), min, max
+        _avg, _min, _max = [
+          float(x) for x in df[['avg','min','max']].iloc[0]
+        ]
+        self.rating.update_rating(_avg, _min, _max, unit, price=price, quantity=quantity)
+        self.update_graph(df)
 
 
     def __init__(self,
@@ -217,7 +218,7 @@ class TransactionEditor(FocusWidget):
         }
         self.graph = FlowBarGraphWithVScale(
             50, 14,
-            ['bg','popup_focus', ],
+            ['bg','popup_focus', 'badge_neutral' ],
             hatt=['dark red', 'dark red', 'dark red']
         )
         self.rating = Rating(dict(filter(