mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Workout details: use combined chart for charts fragment, scatter chart for cadence
This commit is contained in:
+2
-2
@@ -1,6 +1,6 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_MINUTES_PER_KM;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SECONDS_PER_KM;
|
||||
|
||||
import com.github.mikephil.charting.formatter.ValueFormatter;
|
||||
|
||||
@@ -17,7 +17,7 @@ public class SpeedYLabelFormatter extends ValueFormatter {
|
||||
|
||||
@Override
|
||||
public String getFormattedValue(float value) {
|
||||
if (unit.equals(UNIT_MINUTES_PER_KM)) {
|
||||
if (unit.equals(UNIT_SECONDS_PER_KM)) {
|
||||
value = value > 0 ? Math.round((60 / (value * 3.6)) * 60) : 0;
|
||||
}
|
||||
return workoutValueFormatter.formatValue(value, unit, false);
|
||||
|
||||
+5
-5
@@ -5,11 +5,11 @@ import android.graphics.Canvas;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.github.mikephil.charting.components.MarkerView;
|
||||
import com.github.mikephil.charting.data.CombinedData;
|
||||
import com.github.mikephil.charting.data.Entry;
|
||||
import com.github.mikephil.charting.data.LineData;
|
||||
import com.github.mikephil.charting.formatter.ValueFormatter;
|
||||
import com.github.mikephil.charting.highlight.Highlight;
|
||||
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
|
||||
import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet;
|
||||
import com.github.mikephil.charting.utils.MPPointF;
|
||||
|
||||
import java.util.List;
|
||||
@@ -20,14 +20,14 @@ public class ValueMarker extends MarkerView {
|
||||
private TextView markerContent;
|
||||
private List<ValueFormatter> valueFormatters;
|
||||
private List<String> valueUnits;
|
||||
private LineData lineData;
|
||||
private CombinedData lineData;
|
||||
|
||||
public ValueMarker(Context context) {
|
||||
super(context, R.layout.value_marker);
|
||||
this.markerContent = findViewById(R.id.marker_content);
|
||||
}
|
||||
|
||||
public ValueMarker(Context context, LineData lineData, List<ValueFormatter> valueFormatters, List<String> valueUnits) {
|
||||
public ValueMarker(Context context, CombinedData lineData, List<ValueFormatter> valueFormatters, List<String> valueUnits) {
|
||||
super(context, R.layout.value_marker);
|
||||
this.markerContent = findViewById(R.id.marker_content);
|
||||
this.valueFormatters = valueFormatters;
|
||||
@@ -40,7 +40,7 @@ public class ValueMarker extends MarkerView {
|
||||
float xVal = e.getX();
|
||||
StringBuilder content = new StringBuilder();
|
||||
for (int i = 0; i < lineData.getDataSetCount(); i++) {
|
||||
ILineDataSet dataSet = lineData.getDataSetByIndex(i);
|
||||
IBarLineScatterCandleBubbleDataSet dataSet = lineData.getDataSetByIndex(i);
|
||||
if (dataSet == null || !dataSet.isVisible()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
+17
-3
@@ -45,9 +45,12 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.gridlayout.widget.GridLayout
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.github.mikephil.charting.charts.BarLineChartBase
|
||||
import com.github.mikephil.charting.charts.LineChart
|
||||
import com.github.mikephil.charting.charts.ScatterChart
|
||||
import com.github.mikephil.charting.components.XAxis
|
||||
import com.github.mikephil.charting.data.LineData
|
||||
import com.github.mikephil.charting.data.ScatterData
|
||||
import com.github.mikephil.charting.listener.ChartTouchListener
|
||||
import com.github.mikephil.charting.listener.OnChartGestureListener
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -67,6 +70,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.Activity
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummarySimpleEntry
|
||||
import nodomain.freeyourgadget.gadgetbridge.databinding.FragmentWorkoutDetailsBinding
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData
|
||||
@@ -434,7 +438,10 @@ class WorkoutDetailsFragment : Fragment(), MenuProvider {
|
||||
}
|
||||
|
||||
val chartTextColor = GBApplication.getSecondaryTextColor(context);
|
||||
val lineChart = LineChart(requireContext()).apply {
|
||||
val lineChart: BarLineChartBase<*> = when (chart.chartData) {
|
||||
is ScatterData -> ScatterChart(requireContext())
|
||||
else -> LineChart(requireContext())
|
||||
}.apply {
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT
|
||||
@@ -468,7 +475,14 @@ class WorkoutDetailsFragment : Fragment(), MenuProvider {
|
||||
isEnabled = false
|
||||
}
|
||||
chart.lineChart(lineChart);
|
||||
lineChart.data = chart.chartData as LineData?
|
||||
when {
|
||||
lineChart is LineChart && chart.chartData is LineData -> {
|
||||
lineChart.data = chart.chartData
|
||||
}
|
||||
lineChart is ScatterChart && chart.chartData is ScatterData -> {
|
||||
lineChart.data = chart.chartData
|
||||
}
|
||||
}
|
||||
lineChart.description.isEnabled = false;
|
||||
lineChart.onChartGestureListener = object : OnChartGestureListener {
|
||||
override fun onChartLongPressed(me: MotionEvent?) {}
|
||||
@@ -809,7 +823,7 @@ class WorkoutDetailsFragment : Fragment(), MenuProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getGBDevice(device: nodomain.freeyourgadget.gadgetbridge.entities.Device?): GBDevice? {
|
||||
private fun getGBDevice(device: Device?): GBDevice? {
|
||||
return device?.let { findDevice ->
|
||||
GBApplication.app().deviceManager.devices
|
||||
.firstOrNull { it.address.equals(findDevice.identifier, ignoreCase = true) }
|
||||
|
||||
-1
@@ -123,7 +123,6 @@ public class WorkoutValueFormatter {
|
||||
}
|
||||
break;
|
||||
case UNIT_SECONDS_PER_KM:
|
||||
case UNIT_MINUTES_PER_KM:
|
||||
if (units.equals(UNIT_IMPERIAL)) {
|
||||
value = value / 60D * 1.609344;
|
||||
unit = "minutes_mi";
|
||||
|
||||
+31
-24
@@ -1,19 +1,22 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.workouts.charts;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_BPM;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_KMPH;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_METERS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_METERS_PER_SECOND;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_MINUTES_PER_KM;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SECONDS_PER_KM;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SPM;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
|
||||
import com.github.mikephil.charting.components.LegendEntry;
|
||||
import com.github.mikephil.charting.charts.ScatterChart;
|
||||
import com.github.mikephil.charting.components.YAxis;
|
||||
import com.github.mikephil.charting.data.Entry;
|
||||
import com.github.mikephil.charting.data.LineData;
|
||||
import com.github.mikephil.charting.data.LineDataSet;
|
||||
import com.github.mikephil.charting.data.ScatterData;
|
||||
import com.github.mikephil.charting.data.ScatterDataSet;
|
||||
import com.github.mikephil.charting.formatter.ValueFormatter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -68,7 +71,7 @@ public class DefaultWorkoutCharts {
|
||||
|
||||
if (!heartRateDataPoints.isEmpty()) {
|
||||
final String label = String.format("%s(%s)", context.getString(R.string.heart_rate), getUnitString(context, UNIT_BPM));
|
||||
final LineDataSet dataset = createDataSet(context, heartRateDataPoints, label, context.getResources().getColor(R.color.chart_line_heart_rate));
|
||||
final LineDataSet dataset = createLineDataSet(context, heartRateDataPoints, label, context.getResources().getColor(R.color.chart_line_heart_rate));
|
||||
ValueFormatter integerFormatter = new ValueFormatter() {
|
||||
@Override
|
||||
public String getFormattedValue(float value) {
|
||||
@@ -81,24 +84,18 @@ public class DefaultWorkoutCharts {
|
||||
if (hasSpeedValues && !speedDataPoints.isEmpty()) {
|
||||
if (ActivityKind.isPaceActivity(activityKind)) {
|
||||
final String label = String.format("%s (%s)", context.getString(R.string.Pace), getUnitString(context, UNIT_MINUTES_PER_KM));
|
||||
final LineDataSet dataset = createDataSet(context, speedDataPoints, label, context.getResources().getColor(R.color.chart_line_speed));
|
||||
charts.add(new WorkoutChart("pace", context.getString(R.string.Pace), ActivitySummaryEntries.GROUP_SPEED, new LineData(dataset), new SpeedYLabelFormatter(UNIT_MINUTES_PER_KM), getUnitString(context, UNIT_MINUTES_PER_KM)));
|
||||
final LineDataSet dataset = createLineDataSet(context, speedDataPoints, label, context.getResources().getColor(R.color.chart_line_speed));
|
||||
charts.add(new WorkoutChart("pace", context.getString(R.string.Pace), ActivitySummaryEntries.GROUP_SPEED, new LineData(dataset), new SpeedYLabelFormatter(UNIT_SECONDS_PER_KM), getUnitString(context, UNIT_MINUTES_PER_KM)));
|
||||
} else {
|
||||
final String label = String.format("%s (%s)", context.getString(R.string.Speed), getUnitString(context, UNIT_METERS_PER_SECOND));
|
||||
final LineDataSet dataset = createDataSet(context, speedDataPoints, label, context.getResources().getColor(R.color.chart_line_speed));
|
||||
charts.add(new WorkoutChart("speed", context.getString(R.string.Speed), ActivitySummaryEntries.GROUP_SPEED, new LineData(dataset), new SpeedYLabelFormatter(UNIT_METERS_PER_SECOND), getUnitString(context, UNIT_METERS_PER_SECOND)));
|
||||
final String label = String.format("%s (%s)", context.getString(R.string.Speed), getUnitString(context, UNIT_KMPH));
|
||||
final LineDataSet dataset = createLineDataSet(context, speedDataPoints, label, context.getResources().getColor(R.color.chart_line_speed));
|
||||
charts.add(new WorkoutChart("speed", context.getString(R.string.Speed), ActivitySummaryEntries.GROUP_SPEED, new LineData(dataset), new SpeedYLabelFormatter(UNIT_METERS_PER_SECOND), getUnitString(context, UNIT_KMPH)));
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCadenceValues && !cadenceDataPoints.isEmpty()) {
|
||||
final String label = String.format("%s (%s)", context.getString(R.string.workout_cadence), getUnitString(context, getCadenceUnit(cycleUnit)));
|
||||
final LineDataSet dataset = createDataSet(context, cadenceDataPoints, label, context.getResources().getColor(R.color.transparent));
|
||||
dataset.setDrawCircles(true);
|
||||
dataset.setDrawCircleHole(false);
|
||||
dataset.setLineWidth(-1);
|
||||
dataset.setCircleRadius(3f);
|
||||
dataset.setMode(LineDataSet.Mode.CUBIC_BEZIER);
|
||||
dataset.setCircleColor(context.getResources().getColor(R.color.chart_cadence_circle));
|
||||
final ScatterDataSet dataset = createScatterDataSet(context, cadenceDataPoints, label, context.getResources().getColor(R.color.chart_cadence_circle));
|
||||
final ValueFormatter integerFormatter = new ValueFormatter() {
|
||||
@Override
|
||||
public String getFormattedValue(float value) {
|
||||
@@ -110,7 +107,7 @@ public class DefaultWorkoutCharts {
|
||||
"cadence",
|
||||
context.getString(R.string.workout_cadence),
|
||||
ActivitySummaryEntries.GROUP_CADENCE,
|
||||
new LineData(dataset),
|
||||
new ScatterData(dataset),
|
||||
integerFormatter,
|
||||
getUnitString(context, UNIT_SPM),
|
||||
lineChart -> {
|
||||
@@ -120,12 +117,6 @@ public class DefaultWorkoutCharts {
|
||||
YAxis yAxisRight = lineChart.getAxisRight();
|
||||
yAxisRight.setAxisMinimum(0);
|
||||
yAxisRight.setAxisMaximum(xAxisMaximum);
|
||||
List<LegendEntry> legendEntries = new ArrayList<>(1);
|
||||
LegendEntry hrEntry = new LegendEntry();
|
||||
hrEntry.label = label;
|
||||
hrEntry.formColor = context.getResources().getColor(R.color.chart_cadence_circle);
|
||||
legendEntries.add(hrEntry);
|
||||
lineChart.getLegend().setCustom(legendEntries);
|
||||
return kotlin.Unit.INSTANCE;
|
||||
}
|
||||
)
|
||||
@@ -134,7 +125,7 @@ public class DefaultWorkoutCharts {
|
||||
|
||||
if (!elevationDataPoints.isEmpty()) {
|
||||
final String label = String.format("%s (%s)", context.getString(R.string.Elevation), getUnitString(context, UNIT_METERS));
|
||||
LineDataSet dataset = createDataSet(context, elevationDataPoints, label, context.getResources().getColor(R.color.chart_line_elevation));
|
||||
LineDataSet dataset = createLineDataSet(context, elevationDataPoints, label, context.getResources().getColor(R.color.chart_line_elevation));
|
||||
charts.add(new WorkoutChart("elevation", context.getString(R.string.Elevation), ActivitySummaryEntries.GROUP_ELEVATION, new LineData(dataset), null, getUnitString(context, UNIT_METERS)));
|
||||
}
|
||||
|
||||
@@ -149,7 +140,7 @@ public class DefaultWorkoutCharts {
|
||||
return "";
|
||||
}
|
||||
|
||||
public static LineDataSet createDataSet(final Context context,
|
||||
public static LineDataSet createLineDataSet(final Context context,
|
||||
final List<Entry> entities,
|
||||
final String label,
|
||||
final int color) {
|
||||
@@ -167,6 +158,22 @@ public class DefaultWorkoutCharts {
|
||||
return dataSet;
|
||||
}
|
||||
|
||||
public static ScatterDataSet createScatterDataSet(final Context context,
|
||||
final List<Entry> entities,
|
||||
final String label,
|
||||
final int color) {
|
||||
final ScatterDataSet dataSet = new ScatterDataSet(entities, label);
|
||||
dataSet.setAxisDependency(YAxis.AxisDependency.RIGHT);
|
||||
dataSet.setColor(color);
|
||||
dataSet.setValueTextColor(GBApplication.getSecondaryTextColor(context));
|
||||
dataSet.setHighlightLineWidth(2f);
|
||||
dataSet.setDrawValues(false);
|
||||
dataSet.setDrawHorizontalHighlightIndicator(false);
|
||||
dataSet.setScatterShape(ScatterChart.ScatterShape.CIRCLE);
|
||||
dataSet.setScatterShapeSize(10f);
|
||||
return dataSet;
|
||||
}
|
||||
|
||||
public static String getCadenceUnit(final ActivityKind.CycleUnit unit) {
|
||||
return switch (unit) {
|
||||
case STROKES -> ActivitySummaryEntries.UNIT_STROKES_PER_MINUTE;
|
||||
|
||||
+22
-7
@@ -12,11 +12,15 @@ import androidx.core.view.MenuProvider
|
||||
import androidx.core.view.children
|
||||
import com.github.mikephil.charting.components.XAxis
|
||||
import com.github.mikephil.charting.components.YAxis
|
||||
import com.github.mikephil.charting.data.CombinedData
|
||||
import com.github.mikephil.charting.data.Entry
|
||||
import com.github.mikephil.charting.data.LineData
|
||||
import com.github.mikephil.charting.data.LineDataSet
|
||||
import com.github.mikephil.charting.data.LineScatterCandleRadarDataSet
|
||||
import com.github.mikephil.charting.data.ScatterData
|
||||
import com.github.mikephil.charting.data.ScatterDataSet
|
||||
import com.github.mikephil.charting.formatter.DefaultAxisValueFormatter
|
||||
import com.github.mikephil.charting.formatter.ValueFormatter
|
||||
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
@@ -126,17 +130,27 @@ class WorkoutChartsActivity : AbstractGBActivity(), MenuProvider {
|
||||
}
|
||||
|
||||
fun refreshChart() {
|
||||
val lineDataSets = mutableListOf<ILineDataSet>()
|
||||
val combinedData = CombinedData();
|
||||
val lineData = LineData()
|
||||
val scatterData = ScatterData()
|
||||
val lineDataSetsMarkerFormatters = mutableListOf<ValueFormatter?>()
|
||||
val lineDataSetsMarkerUnits = mutableListOf<String?>()
|
||||
var leftY = true
|
||||
selectedCharts.forEach { selectedChart ->
|
||||
val workoutChart = chartData?.find { it.id == selectedChart } ?: return@forEach
|
||||
val dataSet = workoutChart.chartData.getDataSetByIndex(0) as? LineDataSet ?: return@forEach
|
||||
val dataSet = workoutChart.chartData.getDataSetByIndex(0) as? LineScatterCandleRadarDataSet<Entry> ?: return@forEach
|
||||
dataSet.highLightColor = context.getColor(R.color.chart_highline_dolor);
|
||||
dataSet.highlightLineWidth = 1f;
|
||||
dataSet.axisDependency = if(leftY) YAxis.AxisDependency.LEFT else YAxis.AxisDependency.RIGHT;
|
||||
lineDataSets.add(dataSet)
|
||||
when (dataSet) {
|
||||
is LineDataSet -> {
|
||||
lineData.addDataSet(dataSet)
|
||||
}
|
||||
is ScatterDataSet -> {
|
||||
scatterData.addDataSet(dataSet)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
lineDataSetsMarkerFormatters.add(workoutChart.chartYLabelFormatter);
|
||||
lineDataSetsMarkerUnits.add(workoutChart.unitString);
|
||||
val axis = if (leftY) binding.workoutDataChart.axisLeft else binding.workoutDataChart.axisRight
|
||||
@@ -148,9 +162,10 @@ class WorkoutChartsActivity : AbstractGBActivity(), MenuProvider {
|
||||
val workoutChart = chartData?.find { it.id == selectedChartId } ?: return
|
||||
binding.workoutDataChart.axisRight.valueFormatter = workoutChart?.chartYLabelFormatter ?: DefaultAxisValueFormatter(0)
|
||||
}
|
||||
val lineData = LineData(lineDataSets)
|
||||
binding.workoutDataChart.data = lineData;
|
||||
binding.workoutDataChart.marker = ValueMarker(this, lineData, lineDataSetsMarkerFormatters, lineDataSetsMarkerUnits);
|
||||
combinedData.setData(lineData)
|
||||
combinedData.setData(scatterData)
|
||||
binding.workoutDataChart.data = combinedData;
|
||||
binding.workoutDataChart.marker = ValueMarker(this, combinedData, lineDataSetsMarkerFormatters, lineDataSetsMarkerUnits);
|
||||
binding.workoutDataChart.highlightValues(null)
|
||||
binding.workoutDataChart.invalidate()
|
||||
}
|
||||
|
||||
+5
-3
@@ -1,15 +1,17 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.model.workout
|
||||
|
||||
import com.github.mikephil.charting.charts.LineChart
|
||||
import com.github.mikephil.charting.charts.BarLineChartBase
|
||||
import com.github.mikephil.charting.data.ChartData
|
||||
import com.github.mikephil.charting.data.Entry
|
||||
import com.github.mikephil.charting.formatter.ValueFormatter
|
||||
import com.github.mikephil.charting.interfaces.datasets.IDataSet
|
||||
|
||||
data class WorkoutChart @JvmOverloads constructor(
|
||||
val id: String,
|
||||
val title: String,
|
||||
val group: String,
|
||||
val chartData: ChartData<*>,
|
||||
val chartData: ChartData<out IDataSet<out Entry>>,
|
||||
var chartYLabelFormatter: ValueFormatter? = null,
|
||||
var unitString: String? = null,
|
||||
val lineChart: (LineChart) -> Unit = {}
|
||||
val lineChart: (BarLineChartBase<*>) -> Unit = {}
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.workouts.charts.WorkoutChartsActivity">
|
||||
|
||||
|
||||
<com.github.mikephil.charting.charts.LineChart
|
||||
<com.github.mikephil.charting.charts.CombinedChart
|
||||
android:id="@+id/workout_data_chart"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="300dp"
|
||||
|
||||
Reference in New Issue
Block a user