Add settings toggles for chart series

Adds toggles to the User Interface section of the app Settings that
allow the user to display or hide the three series available in the
chart: Elevation, Speed/Pace, and Heart Rate. As a user of a heart rate
monitor primarily during strength workouts and combat sports, the
distance and elevation graphs are not helpful to me - in addition, their
scale means that I am not able to easily see my exact heart rate. This
should allow users in a similar situation to better determine exactly
what is most helpful for them to be seen on the graph.

Also updates heart rate series possible intervals - the smallest interval
was 25, which was too low for most zone 1 cardio charts - if your heart
rate goes from 70 to 125, with a 25 interval, most of the chart will be
whitespace.
This commit is contained in:
Vasja Volin
2025-01-22 21:05:17 -08:00
committed by Dennis Guse
parent ae237bdddc
commit eda1815393
6 changed files with 150 additions and 27 deletions
@@ -86,6 +86,7 @@ public class ChartFragment extends Fragment implements TrackDataHub.Listener {
}); });
} }
} }
if (PreferencesUtils.isKey(R.string.stats_rate_key, key)) { if (PreferencesUtils.isKey(R.string.stats_rate_key, key)) {
boolean reportSpeed = PreferencesUtils.isReportSpeed(activityTypeLocalized); boolean reportSpeed = PreferencesUtils.isReportSpeed(activityTypeLocalized);
if (reportSpeed != viewBinding.chartView.getReportSpeed()) { if (reportSpeed != viewBinding.chartView.getReportSpeed()) {
@@ -99,6 +100,21 @@ public class ChartFragment extends Fragment implements TrackDataHub.Listener {
}); });
} }
} }
if (PreferencesUtils.isKey(R.string.chart_display_elevation_key, key)) {
viewBinding.chartView.setShowElevation(PreferencesUtils.shouldShowElevation());
refreshChart();
}
if (PreferencesUtils.isKey(R.string.chart_display_pace_or_speed_key, key)) {
viewBinding.chartView.setShowPaceOrSpeed(PreferencesUtils.shouldShowPaceOrSpeed());
refreshChart();
}
if (PreferencesUtils.isKey(R.string.chart_display_heart_rate_key, key)) {
viewBinding.chartView.setShowHeartRate(PreferencesUtils.shouldShowHeartRate());
refreshChart();
}
} }
}; };
@@ -273,4 +289,13 @@ public class ChartFragment extends Fragment implements TrackDataHub.Listener {
fragmentActivity.runOnUiThread(runnable); fragmentActivity.runOnUiThread(runnable);
} }
} }
private void refreshChart() {
runOnUiThread(() -> {
if (isResumed()) {
viewBinding.chartView.invalidate();
viewBinding.chartView.requestLayout();
}
});
}
} }
@@ -86,8 +86,10 @@ public class ChartView extends View {
} }
private final List<ChartValueSeries> seriesList = new LinkedList<>(); private final List<ChartValueSeries> seriesList = new LinkedList<>();
private final ChartValueSeries elevationSeries;
private final ChartValueSeries speedSeries; private final ChartValueSeries speedSeries;
private final ChartValueSeries paceSeries; private final ChartValueSeries paceSeries;
private final ChartValueSeries heartRateSeries;
private final LinkedList<ChartPoint> chartPoints = new LinkedList<>(); private final LinkedList<ChartPoint> chartPoints = new LinkedList<>();
private final List<Marker> markers = new LinkedList<>(); private final List<Marker> markers = new LinkedList<>();
@@ -123,6 +125,7 @@ public class ChartView extends View {
private boolean chartByDistance = false; private boolean chartByDistance = false;
private UnitSystem unitSystem = UnitSystem.defaultUnitSystem(); private UnitSystem unitSystem = UnitSystem.defaultUnitSystem();
private boolean reportSpeed = true; private boolean reportSpeed = true;
private boolean showPaceOrSpeed = true;
private boolean showPointer = false; private boolean showPointer = false;
private final GestureDetectorCompat detectorScrollFlingTab = new GestureDetectorCompat(getContext(), new GestureDetector.SimpleOnGestureListener() { private final GestureDetectorCompat detectorScrollFlingTab = new GestureDetectorCompat(getContext(), new GestureDetector.SimpleOnGestureListener() {
@@ -176,17 +179,17 @@ public class ChartView extends View {
int fontSizeSmall = ThemeUtils.getFontSizeSmallInPx(context); int fontSizeSmall = ThemeUtils.getFontSizeSmallInPx(context);
int fontSizeMedium = ThemeUtils.getFontSizeMediumInPx(context); int fontSizeMedium = ThemeUtils.getFontSizeMediumInPx(context);
seriesList.add(new ChartValueSeries(context, elevationSeries = new ChartValueSeries(context,
Integer.MIN_VALUE, Integer.MIN_VALUE,
Integer.MAX_VALUE, Integer.MAX_VALUE,
new int[]{5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000}, new int[]{5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000},
R.string.description_altitude_metric, R.string.description_altitude_metric,
R.string.description_altitude_imperial, R.string.description_altitude_imperial,
R.string.description_altitude_imperial, R.string.description_altitude_imperial,
R.color.chart_altitude_fill, R.color.chart_altitude_fill,
R.color.chart_altitude_border, R.color.chart_altitude_border,
fontSizeSmall, fontSizeSmall,
fontSizeMedium) { fontSizeMedium) {
@Override @Override
protected Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) { protected Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
return chartPoint.altitude(); return chartPoint.altitude();
@@ -196,7 +199,8 @@ public class ChartView extends View {
protected boolean drawIfChartPointHasNoData() { protected boolean drawIfChartPointHasNoData() {
return false; return false;
} }
}); };
seriesList.add(elevationSeries);
speedSeries = new ChartValueSeries(context, speedSeries = new ChartValueSeries(context,
0, 0,
@@ -244,10 +248,15 @@ public class ChartView extends View {
}; };
seriesList.add(paceSeries); seriesList.add(paceSeries);
seriesList.add(new ChartValueSeries(context, heartRateSeries = new ChartValueSeries(context,
0, 0,
Integer.MAX_VALUE, Integer.MAX_VALUE,
new int[]{25, 50}, // For Zone 5 cardio, the 25 value should result in nice visuals, as the values
// will range from ~70 - ~180 (around 4.5 intervals).
// For Zone 1 cardio, the 15 value should result in nice visuals, as the values
// will range from ~70 - ~120 (around 3.5 intervals)
// The fallback of 50 should give appropriate visuals for values outside this range.
new int[]{15, 25, 50},
R.string.description_sensor_heart_rate, R.string.description_sensor_heart_rate,
R.string.description_sensor_heart_rate, R.string.description_sensor_heart_rate,
R.string.description_sensor_heart_rate, R.string.description_sensor_heart_rate,
@@ -264,7 +273,8 @@ public class ChartView extends View {
protected boolean drawIfChartPointHasNoData() { protected boolean drawIfChartPointHasNoData() {
return false; return false;
} }
}); };
seriesList.add(heartRateSeries);
seriesList.add(new ChartValueSeries(context, seriesList.add(new ChartValueSeries(context,
0, 0,
@@ -343,9 +353,15 @@ public class ChartView extends View {
setClickable(true); setClickable(true);
updateDimensions(); updateDimensions();
// either speedSeries or paceSeries should be enabled. // Either speedSeries or paceSeries should be enabled, if one is shown.
speedSeries.setEnabled(reportSpeed); if (showPaceOrSpeed) {
paceSeries.setEnabled(!reportSpeed); speedSeries.setEnabled(reportSpeed);
paceSeries.setEnabled(!reportSpeed);
}
// Defaults for our chart series.
heartRateSeries.setEnabled(true);
elevationSeries.setEnabled(true);
} }
@Override @Override
@@ -379,6 +395,12 @@ public class ChartView extends View {
} }
public boolean applyReportSpeed() { public boolean applyReportSpeed() {
if (!showPaceOrSpeed) {
paceSeries.setEnabled(false);
speedSeries.setEnabled(false);
return true;
}
if (reportSpeed) { if (reportSpeed) {
if (!speedSeries.isEnabled()) { if (!speedSeries.isEnabled()) {
speedSeries.setEnabled(true); speedSeries.setEnabled(true);
@@ -396,6 +418,20 @@ public class ChartView extends View {
return false; return false;
} }
void setShowElevation(boolean value) {
elevationSeries.setEnabled(value);
}
void setShowPaceOrSpeed(boolean value) {
showPaceOrSpeed = value;
// we want to make sure we show whatever version the user has
// selected when we turn this back on.
applyReportSpeed();
}
void setShowHeartRate(boolean value) {
heartRateSeries.setEnabled(value);
}
public void setShowPointer(boolean value) { public void setShowPointer(boolean value) {
showPointer = value; showPointer = value;
} }
@@ -644,10 +680,18 @@ public class ChartView extends View {
*/ */
private void drawSeriesTitles(Canvas canvas) { private void drawSeriesTitles(Canvas canvas) {
Iterator<TitlePosition> tpI = titleDimensions.titlePositions.iterator(); Iterator<TitlePosition> tpI = titleDimensions.titlePositions.iterator();
for (ChartValueSeries chartValueSeries : seriesList) { for (ChartValueSeries chartValueSeries : seriesList) {
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) { if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) {
String title = getContext().getString(chartValueSeries.getTitleId(unitSystem)); String title = getContext().getString(chartValueSeries.getTitleId(unitSystem));
Paint paint = chartValueSeries.getTitlePaint(); Paint paint = chartValueSeries.getTitlePaint();
// It is possible for the titlePositions to become empty temporarily, while switching between
// chart screens quickly.
if (!tpI.hasNext()) {
return;
}
TitlePosition tp = tpI.next(); TitlePosition tp = tpI.next();
int y = topBorder - spacer - (titleDimensions.lineCount - tp.line) * (titleDimensions.lineHeight + spacer); int y = topBorder - spacer - (titleDimensions.lineCount - tp.line) * (titleDimensions.lineHeight + spacer);
canvas.drawText(title, tp.xPos + getScrollX(), y, paint); canvas.drawText(title, tp.xPos + getScrollX(), y, paint);
@@ -774,7 +818,7 @@ public class ChartView extends View {
//TODO //TODO
int markerXPosition = x - spacer; int markerXPosition = x - spacer;
int index = titleDimensions.titlePositions.size() - 1; // index only onver the visible chart series int index = titleDimensions.titlePositions.size() - 1; // index only over the visible chart series
final int lastDrawn2ndLineMarkerIndex = getYmarkerCountOn1stLine(); final int lastDrawn2ndLineMarkerIndex = getYmarkerCountOn1stLine();
for (int i = seriesList.size()-1; i>=0 ;--i) { // draw markers from the last series to achieve right alignment for (int i = seriesList.size()-1; i>=0 ;--i) { // draw markers from the last series to achieve right alignment
ChartValueSeries chartValueSeries = seriesList.get(i); ChartValueSeries chartValueSeries = seriesList.get(i);
@@ -318,6 +318,21 @@ public class PreferencesUtils {
return getBoolean(R.string.stats_fullscreen_while_recording_key, DEFAULT); return getBoolean(R.string.stats_fullscreen_while_recording_key, DEFAULT);
} }
public static boolean shouldShowElevation() {
final boolean DEFAULT = resources.getBoolean(R.bool.chart_display_elevation_default);
return getBoolean(R.string.chart_display_elevation_key, DEFAULT);
}
public static boolean shouldShowPaceOrSpeed() {
final boolean DEFAULT = resources.getBoolean(R.bool.chart_display_pace_or_speed_default);
return getBoolean(R.string.chart_display_pace_or_speed_key, DEFAULT);
}
public static boolean shouldShowHeartRate() {
final boolean DEFAULT = resources.getBoolean(R.bool.chart_display_heart_rate_default);
return getBoolean(R.string.chart_display_heart_rate_key, DEFAULT);
}
public static boolean shouldUseDynamicColors() { public static boolean shouldUseDynamicColors() {
final boolean DEFAULT = resources.getBoolean(R.bool.settings_ui_dynamic_colors_default); final boolean DEFAULT = resources.getBoolean(R.bool.settings_ui_dynamic_colors_default);
return getBoolean(R.string.settings_ui_dynamic_colors_key, DEFAULT); return getBoolean(R.string.settings_ui_dynamic_colors_key, DEFAULT);
+23 -8
View File
@@ -1,14 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- Do not translate anything in this file directly. --> <!-- Do not translate anything in this file directly. -->
<string name="stats_show_on_lockscreen_while_recording_key" translatable="false">trackdetail_show_on_lockscreen_while_recording</string>
<bool name="stats_show_on_lockscreen_while_recording_default" translatable="false">false</bool>
<string name="stats_keep_screen_on_while_recording_key" translatable="false">trackdetail_keep_screen_on_while_recording</string>
<bool name="stats_keep_screen_on_while_recording_default" translatable="false">false</bool>
<string name="stats_fullscreen_while_recording_key" translatable="false">trackdetail_fullscreen_while_recording</string>
<bool name="stats_fullscreen_while_recording_default" translatable="false">false</bool>
<!-- Main settings --> <!-- Main settings -->
<string name="settings_defaults_key" translatable="false">settingsDefaults</string> <string name="settings_defaults_key" translatable="false">settingsDefaults</string>
@@ -279,9 +271,32 @@
<string name="show_introduction_screen_key" translatable="false">showIntroduction</string> <string name="show_introduction_screen_key" translatable="false">showIntroduction</string>
<bool name="show_introduction_screen_default">true</bool> <bool name="show_introduction_screen_default">true</bool>
<!-- User Interface Settings -->
<string name="settings_ui_dynamic_colors_key" translatable="false">uiDynamicColors</string> <string name="settings_ui_dynamic_colors_key" translatable="false">uiDynamicColors</string>
<bool name="settings_ui_dynamic_colors_default" translatable="false">false</bool> <bool name="settings_ui_dynamic_colors_default" translatable="false">false</bool>
<!-- Recording Settings -->
<string name="stats_show_on_lockscreen_while_recording_key" translatable="false">trackdetail_show_on_lockscreen_while_recording</string>
<bool name="stats_show_on_lockscreen_while_recording_default" translatable="false">false</bool>
<string name="stats_keep_screen_on_while_recording_key" translatable="false">trackdetail_keep_screen_on_while_recording</string>
<bool name="stats_keep_screen_on_while_recording_default" translatable="false">false</bool>
<string name="stats_fullscreen_while_recording_key" translatable="false">trackdetail_fullscreen_while_recording</string>
<bool name="stats_fullscreen_while_recording_default" translatable="false">false</bool>
<!-- Chart Settings -->
<string name="chart_display_elevation_key" translatable="false">chart_display_elevation</string>
<bool name="chart_display_elevation_default" translatable="false">true</bool>
<string name="chart_display_pace_or_speed_key" translatable="false">chart_display_pace_or_speed</string>
<bool name="chart_display_pace_or_speed_default" translatable="false">true</bool>
<string name="chart_display_heart_rate_key" translatable="false">chart_display_heart_rate</string>
<bool name="chart_display_heart_rate_default" translatable="false">true</bool>
<!-- androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode() --> <!-- androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode() -->
<string name="locale_key" translatable="false">localeKey</string> <string name="locale_key" translatable="false">localeKey</string>
+5
View File
@@ -344,6 +344,11 @@ limitations under the License.
<!-- Settings Chart --> <!-- Settings Chart -->
<string name="settings_chart_by_distance">By distance</string> <string name="settings_chart_by_distance">By distance</string>
<string name="settings_chart_by_time">By time</string> <string name="settings_chart_by_time">By time</string>
<string name="settings_charts_title">Charts</string>
<string name="settings_chart_display_elevation_title">Display elevation</string>
<string name="settings_chart_display_pace_or_speed_title">Display pace or speed</string>
<string name="settings_chart_display_heart_rate_title">Display heart rate</string>
<!-- Settings Recording --> <!-- Settings Recording -->
<string name="settings_recording_title">Recording</string> <string name="settings_recording_title">Recording</string>
<string name="settings_theme_switch_restart">Theme changes may require a manual restart.</string> <string name="settings_theme_switch_restart">Theme changes may require a manual restart.</string>
@@ -58,4 +58,23 @@
android:title="@string/settings_recording_fullscreen_on_while_recording_title" /> android:title="@string/settings_recording_fullscreen_on_while_recording_title" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory app:title="@string/settings_charts_title">
<SwitchPreferenceCompat
android:icon="@drawable/ic_activity_climbing_24dp"
android:defaultValue="@bool/chart_display_elevation_default"
android:key="@string/chart_display_elevation_key"
android:title="@string/settings_chart_display_elevation_title" />
<SwitchPreferenceCompat
android:icon="@drawable/ic_activity_snowboarding_24dp"
android:defaultValue="@bool/chart_display_pace_or_speed_default"
android:key="@string/chart_display_pace_or_speed_key"
android:title="@string/settings_chart_display_pace_or_speed_title" />
<SwitchPreferenceCompat
android:icon="@drawable/ic_activity_workout_24dp"
android:defaultValue="@bool/chart_display_heart_rate_default"
android:key="@string/chart_display_heart_rate_key"
android:title="@string/settings_chart_display_heart_rate_title" />
</PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>