2020-01-09 10:44:32 +01:00
/ * Copyright ( C ) 2015 - 2020 0nse , Alberto , Andreas Shimokawa , Carsten Pfeiffer ,
2019-11-23 21:52:46 +01:00
Daniele Gobbetti , Pavel Elagin , vanous
2017-03-10 14:53:19 +01:00
This file is part of Gadgetbridge .
Gadgetbridge is free software : you can redistribute it and / or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
Gadgetbridge 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 Affero General Public License for more details .
You should have received a copy of the GNU Affero General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>. */
2017-02-21 16:20:42 +01:00
package nodomain.freeyourgadget.gadgetbridge.activities.charts ;
2017-02-26 00:40:50 +01:00
import android.app.Activity ;
2017-02-21 16:20:42 +01:00
import android.graphics.Color ;
import android.os.Bundle ;
import android.view.LayoutInflater ;
import android.view.View ;
import android.view.ViewGroup ;
2018-09-06 15:07:48 +02:00
import android.widget.TextView ;
2017-02-21 16:20:42 +01:00
import com.github.mikephil.charting.charts.BarChart ;
import com.github.mikephil.charting.charts.PieChart ;
import com.github.mikephil.charting.components.LimitLine ;
import com.github.mikephil.charting.components.XAxis ;
import com.github.mikephil.charting.components.YAxis ;
import com.github.mikephil.charting.data.BarData ;
import com.github.mikephil.charting.data.BarDataSet ;
import com.github.mikephil.charting.data.BarEntry ;
2018-09-10 23:38:49 +02:00
import com.github.mikephil.charting.data.ChartData ;
2017-02-21 16:20:42 +01:00
import com.github.mikephil.charting.data.PieData ;
import com.github.mikephil.charting.data.PieDataSet ;
import com.github.mikephil.charting.data.PieEntry ;
2019-08-24 12:41:26 +02:00
import com.github.mikephil.charting.formatter.ValueFormatter ;
2017-02-21 16:20:42 +01:00
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
import java.util.ArrayList ;
import java.util.Calendar ;
import java.util.List ;
import java.util.Locale ;
2019-08-11 12:00:00 +02:00
import nodomain.freeyourgadget.gadgetbridge.GBApplication ;
2017-02-21 16:20:42 +01:00
import nodomain.freeyourgadget.gadgetbridge.R ;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler ;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice ;
2017-02-26 00:40:50 +01:00
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts ;
2017-02-21 16:20:42 +01:00
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample ;
2017-02-26 00:40:50 +01:00
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue ;
2017-02-21 16:20:42 +01:00
public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
protected static final Logger LOG = LoggerFactory . getLogger ( AbstractWeekChartFragment . class ) ;
2019-08-12 23:18:20 +02:00
protected final int TOTAL_DAYS = getRangeDays ( ) ;
2020-04-09 18:22:57 +02:00
protected int TOTAL_DAYS_FOR_AVERAGE = 0 ;
2017-02-21 16:20:42 +01:00
private Locale mLocale ;
2018-09-10 23:38:49 +02:00
private int mTargetValue = 0 ;
2017-02-21 16:20:42 +01:00
private PieChart mTodayPieChart ;
private BarChart mWeekChart ;
2018-09-06 15:07:48 +02:00
private TextView mBalanceView ;
2017-02-21 16:20:42 +01:00
2017-03-03 17:33:00 +01:00
private int mOffsetHours = getOffsetHours ( ) ;
2017-02-21 16:20:42 +01:00
@Override
protected ChartsData refreshInBackground ( ChartsHost chartsHost , DBHandler db , GBDevice device ) {
Calendar day = Calendar . getInstance ( ) ;
day . setTime ( chartsHost . getEndDate ( ) ) ;
//NB: we could have omitted the day, but this way we can move things to the past easily
DayData dayData = refreshDayPie ( db , day , device ) ;
2018-09-10 23:38:49 +02:00
WeekChartsData weekBeforeData = refreshWeekBeforeData ( db , mWeekChart , day , device ) ;
2017-02-21 16:20:42 +01:00
return new MyChartsData ( dayData , weekBeforeData ) ;
}
@Override
protected void updateChartsnUIThread ( ChartsData chartsData ) {
MyChartsData mcd = ( MyChartsData ) chartsData ;
2017-03-31 23:42:25 +02:00
setupLegend ( mWeekChart ) ;
2017-02-23 21:15:57 +01:00
mTodayPieChart . setCenterText ( mcd . getDayData ( ) . centerText ) ;
2017-02-21 16:20:42 +01:00
mTodayPieChart . setData ( mcd . getDayData ( ) . data ) ;
2019-08-24 11:58:32 +02:00
//set custom renderer for 30days bar charts
if ( GBApplication . getPrefs ( ) . getBoolean ( " charts_range " , true ) ) {
mWeekChart . setRenderer ( new AngledLabelsChartRenderer ( mWeekChart , mWeekChart . getAnimator ( ) , mWeekChart . getViewPortHandler ( ) ) ) ;
}
2017-02-21 16:20:42 +01:00
mWeekChart . setData ( null ) ; // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
mWeekChart . setData ( mcd . getWeekBeforeData ( ) . getData ( ) ) ;
mWeekChart . getXAxis ( ) . setValueFormatter ( mcd . getWeekBeforeData ( ) . getXValueFormatter ( ) ) ;
2018-09-10 23:38:49 +02:00
mBalanceView . setText ( mcd . getWeekBeforeData ( ) . getBalanceMessage ( ) ) ;
2017-02-21 16:20:42 +01:00
}
@Override
protected void renderCharts ( ) {
mWeekChart . invalidate ( ) ;
mTodayPieChart . invalidate ( ) ;
2018-09-10 23:38:49 +02:00
// mBalanceView.setText(getBalanceMessage(balance));
2017-02-21 16:20:42 +01:00
}
2019-08-12 23:18:20 +02:00
private String getWeeksChartsLabel ( Calendar day ) {
if ( GBApplication . getPrefs ( ) . getBoolean ( " charts_range " , true ) ) {
//month, show day date
return String . valueOf ( day . get ( Calendar . DAY_OF_MONTH ) ) ;
}
else {
//week, show short day name
return day . getDisplayName ( Calendar . DAY_OF_WEEK , Calendar . SHORT , mLocale ) ;
}
}
2018-09-10 23:38:49 +02:00
private WeekChartsData < BarData > refreshWeekBeforeData ( DBHandler db , BarChart barChart , Calendar day , GBDevice device ) {
2017-02-21 16:20:42 +01:00
day = ( Calendar ) day . clone ( ) ; // do not modify the caller's argument
2018-09-06 15:07:48 +02:00
day . add ( Calendar . DATE , - TOTAL_DAYS ) ;
2017-02-21 16:20:42 +01:00
List < BarEntry > entries = new ArrayList < > ( ) ;
ArrayList < String > labels = new ArrayList < String > ( ) ;
2018-09-16 13:37:08 +02:00
long balance = 0 ;
2020-08-26 04:34:41 +02:00
long daily_balance = 0 ;
2020-04-11 13:47:13 +02:00
TOTAL_DAYS_FOR_AVERAGE = 0 ;
2020-04-09 18:22:57 +02:00
2018-09-06 15:07:48 +02:00
for ( int counter = 0 ; counter < TOTAL_DAYS ; counter + + ) {
2017-02-26 00:40:50 +01:00
ActivityAmounts amounts = getActivityAmountsForDay ( db , day , device ) ;
2020-04-09 18:22:57 +02:00
daily_balance = calculateBalance ( amounts ) ;
2020-08-26 04:34:41 +02:00
if ( daily_balance > 0 ) {
2020-04-09 18:22:57 +02:00
TOTAL_DAYS_FOR_AVERAGE + + ;
}
2017-02-26 00:40:50 +01:00
2020-04-09 18:22:57 +02:00
balance + = daily_balance ;
2017-02-26 21:41:27 +01:00
entries . add ( new BarEntry ( counter , getTotalsForActivityAmounts ( amounts ) ) ) ;
2019-08-12 23:18:20 +02:00
labels . add ( getWeeksChartsLabel ( day ) ) ;
2017-02-21 16:20:42 +01:00
day . add ( Calendar . DATE , 1 ) ;
}
BarDataSet set = new BarDataSet ( entries , " " ) ;
2017-02-26 21:41:27 +01:00
set . setColors ( getColors ( ) ) ;
2017-03-03 14:21:59 +01:00
set . setValueFormatter ( getBarValueFormatter ( ) ) ;
2017-02-21 16:20:42 +01:00
BarData barData = new BarData ( set ) ;
barData . setValueTextColor ( Color . GRAY ) ; //prevent tearing other graph elements with the black text. Another approach would be to hide the values cmpletely with data.setDrawValues(false);
2017-04-08 22:58:58 +02:00
barData . setValueTextSize ( 10f ) ;
2017-02-21 16:20:42 +01:00
LimitLine target = new LimitLine ( mTargetValue ) ;
barChart . getAxisLeft ( ) . removeAllLimitLines ( ) ;
barChart . getAxisLeft ( ) . addLimitLine ( target ) ;
2019-08-11 00:57:07 +02:00
float average = 0 ;
2020-04-09 18:22:57 +02:00
if ( TOTAL_DAYS_FOR_AVERAGE > 0 ) {
average = Math . abs ( balance / TOTAL_DAYS_FOR_AVERAGE ) ;
2019-08-11 00:57:07 +02:00
}
LimitLine average_line = new LimitLine ( average ) ;
2019-08-11 12:00:00 +02:00
average_line . setLabel ( getString ( R . string . average , getAverage ( average ) ) ) ;
2019-08-11 00:57:07 +02:00
if ( average > ( mTargetValue ) ) {
average_line . setLineColor ( Color . GREEN ) ;
average_line . setTextColor ( Color . GREEN ) ;
}
else {
average_line . setLineColor ( Color . RED ) ;
average_line . setTextColor ( Color . RED ) ;
}
if ( average > 0 ) {
2019-08-11 12:00:00 +02:00
if ( GBApplication . getPrefs ( ) . getBoolean ( " charts_show_average " , true ) ) {
barChart . getAxisLeft ( ) . addLimitLine ( average_line ) ;
}
2019-08-11 00:57:07 +02:00
}
return new WeekChartsData ( barData , new PreformattedXIndexLabelFormatter ( labels ) , getBalanceMessage ( balance , mTargetValue ) ) ;
2017-02-21 16:20:42 +01:00
}
private DayData refreshDayPie ( DBHandler db , Calendar day , GBDevice device ) {
PieData data = new PieData ( ) ;
List < PieEntry > entries = new ArrayList < > ( ) ;
2017-02-26 21:41:27 +01:00
PieDataSet set = new PieDataSet ( entries , " " ) ;
ActivityAmounts amounts = getActivityAmountsForDay ( db , day , device ) ;
float totalValues [ ] = getTotalsForActivityAmounts ( amounts ) ;
2017-03-31 23:42:25 +02:00
String [ ] pieLabels = getPieLabels ( ) ;
2017-02-26 21:41:27 +01:00
float totalValue = 0 ;
2017-03-31 23:42:25 +02:00
for ( int i = 0 ; i < totalValues . length ; i + + ) {
float value = totalValues [ i ] ;
2017-02-26 21:41:27 +01:00
totalValue + = value ;
2017-03-31 23:42:25 +02:00
entries . add ( new PieEntry ( value , pieLabels [ i ] ) ) ;
2017-02-26 21:41:27 +01:00
}
2017-02-21 16:20:42 +01:00
2017-02-26 21:41:27 +01:00
set . setColors ( getColors ( ) ) ;
2017-02-21 16:20:42 +01:00
2017-03-31 23:42:25 +02:00
if ( totalValues . length < 2 ) {
if ( totalValue < mTargetValue ) {
entries . add ( new PieEntry ( ( mTargetValue - totalValue ) ) ) ;
set . addColor ( Color . GRAY ) ;
}
2017-02-21 16:20:42 +01:00
}
data . setDataSet ( set ) ;
2017-03-31 23:42:25 +02:00
if ( totalValues . length < 2 ) {
data . setDrawValues ( false ) ;
}
2017-04-05 22:42:54 +02:00
else {
set . setXValuePosition ( PieDataSet . ValuePosition . OUTSIDE_SLICE ) ;
set . setYValuePosition ( PieDataSet . ValuePosition . OUTSIDE_SLICE ) ;
set . setValueTextColor ( DESCRIPTION_COLOR ) ;
set . setValueTextSize ( 13f ) ;
set . setValueFormatter ( getPieValueFormatter ( ) ) ;
}
2017-02-21 16:20:42 +01:00
2018-09-16 13:37:08 +02:00
return new DayData ( data , formatPieValue ( ( long ) totalValue ) ) ;
2017-02-21 16:20:42 +01:00
}
@Override
public View onCreateView ( LayoutInflater inflater , ViewGroup container ,
Bundle savedInstanceState ) {
mLocale = getResources ( ) . getConfiguration ( ) . locale ;
View rootView = inflater . inflate ( R . layout . fragment_weeksteps_chart , container , false ) ;
int goal = getGoal ( ) ;
if ( goal > = 0 ) {
mTargetValue = goal ;
}
2018-09-06 15:07:48 +02:00
mTodayPieChart = rootView . findViewById ( R . id . todaystepschart ) ;
mWeekChart = rootView . findViewById ( R . id . weekstepschart ) ;
mBalanceView = rootView . findViewById ( R . id . balance ) ;
2017-02-21 16:20:42 +01:00
setupWeekChart ( ) ;
setupTodayPieChart ( ) ;
// refresh immediately instead of use refreshIfVisible(), for perceived performance
refresh ( ) ;
return rootView ;
}
private void setupTodayPieChart ( ) {
mTodayPieChart . setBackgroundColor ( BACKGROUND_COLOR ) ;
mTodayPieChart . getDescription ( ) . setTextColor ( DESCRIPTION_COLOR ) ;
2017-04-08 15:50:13 +02:00
mTodayPieChart . setEntryLabelColor ( DESCRIPTION_COLOR ) ;
2017-03-03 14:21:59 +01:00
mTodayPieChart . getDescription ( ) . setText ( getPieDescription ( mTargetValue ) ) ;
2017-02-21 16:20:42 +01:00
// mTodayPieChart.setNoDataTextDescription("");
mTodayPieChart . setNoDataText ( " " ) ;
mTodayPieChart . getLegend ( ) . setEnabled ( false ) ;
}
private void setupWeekChart ( ) {
mWeekChart . setBackgroundColor ( BACKGROUND_COLOR ) ;
mWeekChart . getDescription ( ) . setTextColor ( DESCRIPTION_COLOR ) ;
mWeekChart . getDescription ( ) . setText ( " " ) ;
mWeekChart . setFitBars ( true ) ;
configureBarLineChartDefaults ( mWeekChart ) ;
XAxis x = mWeekChart . getXAxis ( ) ;
x . setDrawLabels ( true ) ;
x . setDrawGridLines ( false ) ;
x . setEnabled ( true ) ;
x . setTextColor ( CHART_TEXT_COLOR ) ;
x . setDrawLimitLinesBehindData ( true ) ;
x . setPosition ( XAxis . XAxisPosition . BOTTOM ) ;
YAxis y = mWeekChart . getAxisLeft ( ) ;
y . setDrawGridLines ( false ) ;
y . setDrawTopYLabelEntry ( false ) ;
y . setTextColor ( CHART_TEXT_COLOR ) ;
y . setDrawZeroLine ( true ) ;
y . setSpaceBottom ( 0 ) ;
y . setAxisMinimum ( 0 ) ;
2017-03-03 14:21:59 +01:00
y . setValueFormatter ( getYAxisFormatter ( ) ) ;
2017-02-21 16:20:42 +01:00
y . setEnabled ( true ) ;
YAxis yAxisRight = mWeekChart . getAxisRight ( ) ;
yAxisRight . setDrawGridLines ( false ) ;
yAxisRight . setEnabled ( false ) ;
yAxisRight . setDrawLabels ( false ) ;
yAxisRight . setDrawTopYLabelEntry ( false ) ;
yAxisRight . setTextColor ( CHART_TEXT_COLOR ) ;
}
2017-03-03 17:33:00 +01:00
private List < ? extends ActivitySample > getSamplesOfDay ( DBHandler db , Calendar day , int offsetHours , GBDevice device ) {
2017-02-21 16:20:42 +01:00
int startTs ;
int endTs ;
day = ( Calendar ) day . clone ( ) ; // do not modify the caller's argument
day . set ( Calendar . HOUR_OF_DAY , 0 ) ;
day . set ( Calendar . MINUTE , 0 ) ;
day . set ( Calendar . SECOND , 0 ) ;
2017-03-03 17:33:00 +01:00
day . add ( Calendar . HOUR , offsetHours ) ;
2017-02-21 16:20:42 +01:00
2017-03-03 17:33:00 +01:00
startTs = ( int ) ( day . getTimeInMillis ( ) / 1000 ) ;
endTs = startTs + 24 * 60 * 60 - 1 ;
2017-02-21 16:20:42 +01:00
return getSamples ( db , device , startTs , endTs ) ;
}
@Override
protected List < ? extends ActivitySample > getSamples ( DBHandler db , GBDevice device , int tsFrom , int tsTo ) {
return super . getAllSamples ( db , device , tsFrom , tsTo ) ;
}
private static class DayData {
private final PieData data ;
2017-02-23 21:15:57 +01:00
private final CharSequence centerText ;
2017-02-21 16:20:42 +01:00
2017-02-23 21:15:57 +01:00
DayData ( PieData data , String centerText ) {
2017-02-21 16:20:42 +01:00
this . data = data ;
2017-02-23 21:15:57 +01:00
this . centerText = centerText ;
2017-02-21 16:20:42 +01:00
}
}
private static class MyChartsData extends ChartsData {
2018-09-10 23:38:49 +02:00
private final WeekChartsData < BarData > weekBeforeData ;
2017-02-21 16:20:42 +01:00
private final DayData dayData ;
2018-09-10 23:38:49 +02:00
MyChartsData ( DayData dayData , WeekChartsData < BarData > weekBeforeData ) {
2017-02-21 16:20:42 +01:00
this . dayData = dayData ;
this . weekBeforeData = weekBeforeData ;
}
DayData getDayData ( ) {
return dayData ;
}
2018-09-10 23:38:49 +02:00
WeekChartsData < BarData > getWeekBeforeData ( ) {
2017-02-21 16:20:42 +01:00
return weekBeforeData ;
}
}
2017-02-26 00:40:50 +01:00
private ActivityAmounts getActivityAmountsForDay ( DBHandler db , Calendar day , GBDevice device ) {
LimitedQueue activityAmountCache = null ;
ActivityAmounts amounts = null ;
Activity activity = getActivity ( ) ;
2017-03-18 17:13:35 +01:00
int key = ( int ) ( day . getTimeInMillis ( ) / 1000 ) + ( mOffsetHours * 3600 ) ;
2017-02-26 00:40:50 +01:00
if ( activity ! = null ) {
activityAmountCache = ( ( ChartsActivity ) activity ) . mActivityAmountCache ;
2017-03-18 17:13:35 +01:00
amounts = ( ActivityAmounts ) ( activityAmountCache . lookup ( key ) ) ;
2017-02-26 00:40:50 +01:00
}
if ( amounts = = null ) {
ActivityAnalysis analysis = new ActivityAnalysis ( ) ;
2017-03-03 17:33:00 +01:00
amounts = analysis . calculateActivityAmounts ( getSamplesOfDay ( db , day , mOffsetHours , device ) ) ;
2017-02-26 00:40:50 +01:00
if ( activityAmountCache ! = null ) {
2017-03-18 17:13:35 +01:00
activityAmountCache . add ( key , amounts ) ;
2017-02-26 00:40:50 +01:00
}
}
return amounts ;
}
2019-08-12 23:18:20 +02:00
private int getRangeDays ( ) {
if ( GBApplication . getPrefs ( ) . getBoolean ( " charts_range " , true ) ) {
return 30 ; }
else {
return 7 ;
}
}
2019-08-11 12:00:00 +02:00
abstract String getAverage ( float value ) ;
2017-02-21 16:20:42 +01:00
abstract int getGoal ( ) ;
2017-03-03 17:33:00 +01:00
abstract int getOffsetHours ( ) ;
2017-02-26 21:41:27 +01:00
abstract float [ ] getTotalsForActivityAmounts ( ActivityAmounts activityAmounts ) ;
2017-02-23 08:49:55 +01:00
2018-09-16 13:37:08 +02:00
abstract String formatPieValue ( long value ) ;
2017-03-03 14:21:59 +01:00
2017-03-31 23:42:25 +02:00
abstract String [ ] getPieLabels ( ) ;
2019-08-24 12:41:26 +02:00
abstract ValueFormatter getPieValueFormatter ( ) ;
2017-03-03 14:21:59 +01:00
2019-08-24 12:41:26 +02:00
abstract ValueFormatter getBarValueFormatter ( ) ;
2017-03-03 14:21:59 +01:00
2019-08-24 12:41:26 +02:00
abstract ValueFormatter getYAxisFormatter ( ) ;
2017-02-23 08:49:55 +01:00
2017-02-26 21:41:27 +01:00
abstract int [ ] getColors ( ) ;
2017-03-03 14:21:59 +01:00
abstract String getPieDescription ( int targetValue ) ;
2018-09-06 15:07:48 +02:00
2018-09-16 13:37:08 +02:00
protected abstract long calculateBalance ( ActivityAmounts amounts ) ;
2018-09-10 23:38:49 +02:00
2018-09-16 13:37:08 +02:00
protected abstract String getBalanceMessage ( long balance , int targetValue ) ;
2018-09-10 23:38:49 +02:00
private class WeekChartsData < T extends ChartData < ? > > extends DefaultChartsData < T > {
private final String balanceMessage ;
public WeekChartsData ( T data , PreformattedXIndexLabelFormatter xIndexLabelFormatter , String balanceMessage ) {
super ( data , xIndexLabelFormatter ) ;
this . balanceMessage = balanceMessage ;
}
public String getBalanceMessage ( ) {
return balanceMessage ;
}
}
2017-02-21 16:20:42 +01:00
}