2017-04-14 18:48:09 +00:00
#!/usr/bin/env python3
# Copyright (C) 2017 Christian Thomas Jacobs.
# This file is part of PyQSO.
# PyQSO is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# PyQSO is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with PyQSO. If not, see <http://www.gnu.org/licenses/>.
from gi . repository import Gtk
import logging
2017-04-14 23:55:52 +00:00
from os . path import basename , getmtime , expanduser , dirname , join , realpath
2017-04-14 18:48:09 +00:00
from datetime import datetime , date
try :
import configparser
except ImportError :
import ConfigParser as configparser
try :
import matplotlib
matplotlib . use ( ' Agg ' )
matplotlib . rcParams [ ' font.size ' ] = 10.0
from matplotlib . backends . backend_gtk3cairo import FigureCanvasGTK3Cairo as FigureCanvas
from matplotlib . figure import Figure
from matplotlib . dates import DateFormatter , MonthLocator
have_matplotlib = True
except ImportError as e :
logging . warning ( e )
logging . warning ( " Could not import matplotlib, so you will not be able to plot annual logbook statistics. Check that all the PyQSO dependencies are satisfied. " )
have_matplotlib = False
class Summary ( object ) :
2017-04-14 20:33:16 +00:00
def __init__ ( self , application ) :
2017-04-14 23:02:13 +00:00
""" Create a summary page containing various statistics such as the number of logs in the logbook, the logbook ' s modification date, etc.
2017-04-14 18:48:09 +00:00
2017-04-14 20:33:16 +00:00
: arg application : The PyQSO application containing the main Gtk window , etc .
"""
2017-04-14 18:48:09 +00:00
2017-04-14 20:33:16 +00:00
self . application = application
self . logbook = self . application . logbook
self . builder = self . application . builder
2017-07-05 23:36:17 +00:00
glade_file_path = join ( realpath ( dirname ( __file__ ) ) , " res " , " pyqso.glade " )
2017-04-14 20:33:16 +00:00
self . builder . add_objects_from_file ( glade_file_path , ( " summary_page " , ) )
self . summary_page = self . builder . get_object ( " summary_page " )
self . items = { }
2017-04-14 18:48:09 +00:00
# Database name in large font at the top of the summary page
2017-04-14 20:33:16 +00:00
self . builder . get_object ( " database_name " ) . set_markup ( " <span size= \" x-large \" > %s </span> " % basename ( self . logbook . path ) )
self . items [ " LOG_COUNT " ] = self . builder . get_object ( " log_count " )
self . items [ " QSO_COUNT " ] = self . builder . get_object ( " qso_count " )
self . items [ " DATE_MODIFIED " ] = self . builder . get_object ( " date_modified " )
2017-04-14 18:48:09 +00:00
# Yearly statistics
config = configparser . ConfigParser ( )
have_config = ( config . read ( expanduser ( ' ~/.config/pyqso/preferences.ini ' ) ) != [ ] )
( section , option ) = ( " general " , " show_yearly_statistics " )
if ( have_config and config . has_option ( section , option ) ) :
2017-07-09 10:42:42 +00:00
if ( config . getboolean ( " general " , " show_yearly_statistics " ) and have_matplotlib ) :
2017-04-14 18:48:09 +00:00
hbox = Gtk . HBox ( )
2017-06-24 19:56:04 +00:00
label = Gtk . Label ( label = " Display statistics for year: " , halign = Gtk . Align . START )
2017-04-14 18:48:09 +00:00
hbox . pack_start ( label , False , False , 6 )
2017-04-14 20:33:16 +00:00
year_select = Gtk . ComboBoxText ( )
2017-04-14 21:03:28 +00:00
min_year , max_year = self . get_year_bounds ( )
2017-04-14 18:48:09 +00:00
if min_year and max_year :
for year in range ( max_year , min_year - 1 , - 1 ) :
2017-04-14 20:33:16 +00:00
year_select . append_text ( str ( year ) )
year_select . append_text ( " " )
year_select . connect ( " changed " , self . on_year_changed )
hbox . pack_start ( year_select , False , False , 6 )
self . summary_page . pack_start ( hbox , False , False , 4 )
self . items [ " YEARLY_STATISTICS " ] = Figure ( )
canvas = FigureCanvas ( self . items [ " YEARLY_STATISTICS " ] )
2017-05-30 12:20:47 +00:00
canvas . set_size_request ( 800 , 175 )
2017-04-14 18:48:09 +00:00
canvas . show ( )
2017-05-30 12:20:47 +00:00
self . summary_page . pack_start ( canvas , True , True , 0 )
2017-04-14 18:48:09 +00:00
# Summary tab label and icon.
2017-04-20 21:50:08 +00:00
tab = Gtk . HBox ( homogeneous = False , spacing = 0 )
label = Gtk . Label ( label = " Summary " )
icon = Gtk . Image . new_from_icon_name ( Gtk . STOCK_INDEX , Gtk . IconSize . MENU )
2017-04-14 20:33:16 +00:00
tab . pack_start ( label , False , False , 0 )
tab . pack_start ( icon , False , False , 0 )
tab . show_all ( )
2017-04-14 18:48:09 +00:00
2017-04-14 20:33:16 +00:00
self . logbook . notebook . insert_page ( self . summary_page , tab , 0 ) # Append as a new tab
self . logbook . notebook . show_all ( )
2017-04-14 18:48:09 +00:00
return
def on_year_changed ( self , combo ) :
""" Re-plot the statistics for the year selected by the user. """
# Clear figure
2017-04-14 20:33:16 +00:00
self . items [ " YEARLY_STATISTICS " ] . clf ( )
self . items [ " YEARLY_STATISTICS " ] . canvas . draw ( )
2017-04-14 18:48:09 +00:00
# Get year to show statistics for.
year = combo . get_active_text ( )
try :
year = int ( year )
except ValueError :
# Empty year string.
return
# Number of contacts made each month
2017-04-14 20:33:16 +00:00
contact_count_plot = self . items [ " YEARLY_STATISTICS " ] . add_subplot ( 121 )
contact_count = self . get_annual_contact_count ( year )
2017-04-14 18:48:09 +00:00
# x-axis formatting based on the date
contact_count_plot . bar ( list ( contact_count . keys ( ) ) , list ( contact_count . values ( ) ) , color = " k " , width = 15 , align = " center " )
formatter = DateFormatter ( " % b " )
contact_count_plot . xaxis . set_major_formatter ( formatter )
month_locator = MonthLocator ( )
contact_count_plot . xaxis . set_major_locator ( month_locator )
contact_count_plot . set_ylabel ( " Number of QSOs " )
# Set x-axis upper limit based on the current month.
contact_count_plot . xaxis_date ( )
contact_count_plot . set_xlim ( [ date ( year - 1 , 12 , 16 ) , date ( year , 12 , 15 ) ] ) # Make a bit of space either side of January and December of the selected year.
# Pie chart of all the modes used.
2017-04-14 20:33:16 +00:00
mode_count_plot = self . items [ " YEARLY_STATISTICS " ] . add_subplot ( 122 )
mode_count = self . get_annual_mode_count ( year )
2017-04-14 18:48:09 +00:00
( patches , texts , autotexts ) = mode_count_plot . pie ( list ( mode_count . values ( ) ) , labels = mode_count . keys ( ) , autopct = ' %1.1f %% ' , shadow = False )
for p in patches :
# Make the patches partially transparent.
p . set_alpha ( 0.75 )
mode_count_plot . set_title ( " Modes used " )
2017-04-14 20:33:16 +00:00
self . items [ " YEARLY_STATISTICS " ] . canvas . draw ( )
2017-04-14 18:48:09 +00:00
return
2017-04-14 21:03:28 +00:00
def get_year_bounds ( self ) :
2017-04-14 20:38:42 +00:00
""" Find the years of the oldest and newest QSOs across all logs in the logbook.
2017-04-14 21:02:16 +00:00
: returns : The years of the oldest and newest QSOs . The tuple ( None , None ) is returned if no QSOs have been made or no QSO dates have been specified .
2017-04-14 20:38:42 +00:00
: rtype : tuple
"""
2017-04-14 18:48:09 +00:00
c = self . logbook . connection . cursor ( )
max_years = [ ]
min_years = [ ]
for log in self . logbook . logs :
query = " SELECT min(QSO_DATE), max(QSO_DATE) FROM %s " % ( log . name )
c . execute ( query )
years = c . fetchone ( )
if years [ 0 ] and years [ 1 ] :
min_years . append ( int ( years [ 0 ] [ : 4 ] ) )
max_years . append ( int ( years [ 1 ] [ : 4 ] ) )
2017-04-14 20:38:42 +00:00
if len ( min_years ) == 0 or len ( max_years ) == 0 :
2017-04-14 18:48:09 +00:00
return None , None
else :
# Return the min and max across all logs.
return min ( min_years ) , max ( max_years )
def get_annual_contact_count ( self , year ) :
2017-04-14 20:38:42 +00:00
""" Find the total number of contacts made in each month in the specified year.
: arg int year : The year of interest .
: returns : The total number of contacts made in each month of a given year .
2017-04-14 21:02:16 +00:00
: rtype : dict
2017-04-14 20:38:42 +00:00
"""
2017-04-14 18:48:09 +00:00
contact_count = { }
c = self . logbook . connection . cursor ( )
for log in self . logbook . logs :
query = " SELECT QSO_DATE, count(QSO_DATE) FROM %s WHERE QSO_DATE >= %d 0101 AND QSO_DATE < %d 0101 GROUP by QSO_DATE " % ( log . name , year , year + 1 )
c . execute ( query )
xy = c . fetchall ( )
for i in range ( len ( xy ) ) :
date_str = xy [ i ] [ 0 ]
y = int ( date_str [ 0 : 4 ] )
m = int ( date_str [ 4 : 6 ] )
date = datetime ( y , m , 1 ) # Collect all contacts together by month.
if date in contact_count . keys ( ) :
contact_count [ date ] + = xy [ i ] [ 1 ]
else :
contact_count [ date ] = xy [ i ] [ 1 ]
return contact_count
def get_annual_mode_count ( self , year ) :
2017-04-14 20:38:42 +00:00
""" Find the total number of contacts made with each mode in a specified year.
: arg int year : The year of interest .
: returns : The total number of contacts made with each mode in a given year .
2017-04-14 21:02:16 +00:00
: rtype : dict
2017-04-14 20:38:42 +00:00
"""
2017-04-14 18:48:09 +00:00
mode_count = { }
for log in self . logbook . logs :
query = " SELECT MODE, count(MODE) FROM %s WHERE QSO_DATE >= %d 0101 AND QSO_DATE < %d 0101 GROUP by MODE " % ( log . name , year , year + 1 )
c = self . logbook . connection . cursor ( )
c . execute ( query )
xy = c . fetchall ( )
for i in range ( len ( xy ) ) :
mode = xy [ i ] [ 0 ]
if mode == " " :
mode = " Unspecified "
# Add to running total
if mode in mode_count . keys ( ) :
mode_count [ mode ] + = xy [ i ] [ 1 ]
else :
mode_count [ mode ] = xy [ i ] [ 1 ]
return mode_count
def update ( self ) :
""" Update the information presented on the summary page. """
2017-04-14 20:33:16 +00:00
self . items [ " LOG_COUNT " ] . set_label ( str ( self . logbook . log_count ) )
self . items [ " QSO_COUNT " ] . set_label ( str ( self . logbook . record_count ) )
2017-04-14 18:48:09 +00:00
try :
t = datetime . fromtimestamp ( getmtime ( self . logbook . path ) ) . strftime ( " %d % B % Y @ % H: % M " )
2017-04-14 20:33:16 +00:00
self . items [ " DATE_MODIFIED " ] . set_label ( str ( t ) )
2017-04-14 18:48:09 +00:00
except ( IOError , OSError ) as e :
logging . exception ( e )
return