mirror of
https://github.com/Team4388/RidgeScout.git
synced 2026-06-09 08:38:03 -06:00
Add candlestick charts
This commit is contained in:
@@ -67,8 +67,9 @@ public class ScoutingDataWriter {
|
||||
int version = ((int)objects.get(0).get());
|
||||
|
||||
if(values.length <= version) {
|
||||
AlertManager.addSimpleError("Field version (" +version + ") is too recent as compared to latest version (" + (values.length-1) + ")!");
|
||||
throw new BuiltByteParser.byteParsingExeption();
|
||||
// AlertManager.addSimpleError("Error loading " + filename);
|
||||
AlertManager.error(new BuiltByteParser.byteParsingExeption("Field version (" +version + ") is too recent as compared to latest version (" + (values.length-1) + ")!"));
|
||||
return null;
|
||||
}
|
||||
|
||||
// System.out.println(version);
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TableLayout;
|
||||
|
||||
import com.github.mikephil.charting.charts.LineChart;
|
||||
import com.github.mikephil.charting.charts.PieChart;
|
||||
@@ -24,6 +25,7 @@ import com.ridgebotics.ridgescout.utility.ByteBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class CheckboxType extends FieldType {
|
||||
@@ -204,7 +206,7 @@ public class CheckboxType extends FieldType {
|
||||
parent.addView(chart);
|
||||
}
|
||||
|
||||
public void addDataToTable(LinearLayout parent, List<DataType>[] data){
|
||||
public void addDataToTable(TableLayout parent, Map<Integer, List<DataType>> data){
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TableLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.ridgebotics.ridgescout.types.data.DataType;
|
||||
@@ -27,6 +28,7 @@ import com.github.mikephil.charting.data.PieEntry;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class DropdownType extends FieldType {
|
||||
@@ -232,7 +234,7 @@ public class DropdownType extends FieldType {
|
||||
parent.addView(chart);
|
||||
}
|
||||
|
||||
public void addDataToTable(LinearLayout parent, List<DataType>[] data){
|
||||
public void addDataToTable(TableLayout parent, Map<Integer, List<DataType>> data){
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.ridgebotics.ridgescout.types.input;
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TableLayout;
|
||||
|
||||
import com.ridgebotics.ridgescout.types.data.DataType;
|
||||
import com.ridgebotics.ridgescout.utility.BuiltByteParser;
|
||||
@@ -10,6 +11,7 @@ import com.ridgebotics.ridgescout.utility.ByteBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public abstract class FieldType {
|
||||
@@ -92,29 +94,10 @@ public abstract class FieldType {
|
||||
public abstract void add_history_view(LinearLayout parent, DataType[] data);
|
||||
|
||||
|
||||
public abstract void addDataToTable(LinearLayout parent, List<DataType>[] data);
|
||||
public abstract void addDataToTable(TableLayout parent, Map<Integer, List<DataType>> data);
|
||||
|
||||
|
||||
public abstract String toString(DataType data);
|
||||
|
||||
|
||||
public int[] getNumberBounds(List<DataType>[] data){
|
||||
int min = Integer.MAX_VALUE;
|
||||
int max = Integer.MIN_VALUE;
|
||||
|
||||
for(int teamNum = 0; teamNum < data.length; teamNum++){
|
||||
if(data[teamNum] == null) continue;
|
||||
for(int i = 0; i < data[teamNum].size(); i++){
|
||||
DataType dataPoint = data[teamNum].get(i);
|
||||
if(dataPoint == null || dataPoint.getValueType() != getValueType()) continue;
|
||||
int num = (int) dataPoint.get();
|
||||
if(num > max) max = num;
|
||||
if(num < min) min = num;
|
||||
}
|
||||
}
|
||||
|
||||
return new int[]{min, max};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TableLayout;
|
||||
|
||||
import com.github.mikephil.charting.charts.LineChart;
|
||||
import com.github.mikephil.charting.components.Legend;
|
||||
@@ -21,6 +22,7 @@ import com.ridgebotics.ridgescout.utility.ByteBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class FieldposType extends FieldType {
|
||||
@@ -214,7 +216,7 @@ public class FieldposType extends FieldType {
|
||||
parent.addView(chart);
|
||||
}
|
||||
|
||||
public void addDataToTable(LinearLayout parent, List<DataType>[] data){
|
||||
public void addDataToTable(TableLayout parent, Map<Integer, List<DataType>> data){
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TableLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.github.mikephil.charting.charts.LineChart;
|
||||
@@ -26,6 +27,7 @@ import com.ridgebotics.ridgescout.utility.ByteBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class NumberType extends FieldType {
|
||||
@@ -309,7 +311,7 @@ public class NumberType extends FieldType {
|
||||
parent.addView(chart);
|
||||
}
|
||||
|
||||
public void addDataToTable(LinearLayout parent, List<DataType>[] data){
|
||||
public void addDataToTable(TableLayout parent, Map<Integer, List<DataType>> data){
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TableLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
@@ -23,6 +24,7 @@ import com.google.android.material.slider.Slider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class SliderType extends FieldType {
|
||||
@@ -296,7 +298,7 @@ public class SliderType extends FieldType {
|
||||
parent.addView(chart);
|
||||
}
|
||||
|
||||
public void addDataToTable(LinearLayout parent, List<DataType>[] data){
|
||||
public void addDataToTable(TableLayout parent, Map<Integer, List<DataType>> data){
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,16 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TableLayout;
|
||||
import android.widget.TableRow;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.ridgebotics.ridgescout.types.data.DataType;
|
||||
import com.ridgebotics.ridgescout.types.data.IntType;
|
||||
import com.ridgebotics.ridgescout.ui.CandlestickHeader;
|
||||
import com.ridgebotics.ridgescout.ui.CandlestickView;
|
||||
import com.ridgebotics.ridgescout.ui.data.DataProcessing;
|
||||
import com.ridgebotics.ridgescout.ui.scouting.TallyCounterView;
|
||||
import com.ridgebotics.ridgescout.utility.AlertManager;
|
||||
import com.ridgebotics.ridgescout.utility.BuiltByteParser;
|
||||
import com.ridgebotics.ridgescout.utility.ByteBuilder;
|
||||
import com.github.mikephil.charting.charts.LineChart;
|
||||
@@ -22,7 +26,9 @@ import com.github.mikephil.charting.data.LineData;
|
||||
import com.github.mikephil.charting.data.LineDataSet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class TallyType extends FieldType {
|
||||
@@ -290,23 +296,60 @@ public class TallyType extends FieldType {
|
||||
parent.addView(chart);
|
||||
}
|
||||
|
||||
public void addDataToTable(LinearLayout parent, List<DataType>[] data){
|
||||
int min = Integer.MAX_VALUE;
|
||||
int max = Integer.MIN_VALUE;
|
||||
public void addDataToTable(TableLayout parent, Map<Integer, List<DataType>> data){
|
||||
int[] tmp_abs_bounds = DataProcessing.getNumberBounds(data);
|
||||
int absmin = tmp_abs_bounds[0];
|
||||
int absmax = tmp_abs_bounds[1];
|
||||
|
||||
for(int teamNum = 0; teamNum < data.length; teamNum++){
|
||||
if(data[teamNum] == null) continue;
|
||||
for(int i = 0; i < data[teamNum].size(); i++){
|
||||
DataType dataPoint = data[teamNum].get(i);
|
||||
if(dataPoint == null || dataPoint.getValueType() != getValueType()) continue;
|
||||
int num = (int) dataPoint.get();
|
||||
System.out.println(num);
|
||||
if(num > max) max = num;
|
||||
if(num < min) min = num;
|
||||
}
|
||||
//(int[]) teamData.get(i).get())[0];
|
||||
// AlertManager.alert("Results","Min: " + min + " Max: " + max);
|
||||
|
||||
parent.removeAllViews();
|
||||
|
||||
List<CandlestickView> views = new ArrayList<>();
|
||||
|
||||
for(Integer teamNum : data.keySet()){
|
||||
CandlestickView candlestickView = new CandlestickView(parent.getContext());
|
||||
candlestickView.fromTeamData(data.get(teamNum), teamNum, absmin, absmax);
|
||||
views.add(candlestickView);
|
||||
}
|
||||
|
||||
AlertManager.alert("Results","Min: " + min + " Max: " + max);
|
||||
|
||||
TableRow row = new TableRow(parent.getContext());
|
||||
|
||||
// Make candlestick chart fill full width
|
||||
parent.setColumnStretchable(1, true);
|
||||
|
||||
// Fill in top left cell
|
||||
row.addView(new View(parent.getContext()));
|
||||
|
||||
CandlestickHeader header = new CandlestickHeader(parent.getContext());
|
||||
header.setScale(absmin, absmax);
|
||||
row.addView(header);
|
||||
|
||||
parent.addView(row);
|
||||
|
||||
// parent.addView(new );
|
||||
|
||||
Collections.sort(views, (a, b) -> (int) ((b.average - a.average)*10.f));
|
||||
for(int i = 0; i < views.size(); i++){
|
||||
row = new TableRow(parent.getContext());
|
||||
CandlestickView view = views.get(i);
|
||||
|
||||
TextView teamNum = new TextView(parent.getContext());
|
||||
TableRow.LayoutParams params = new TableRow.LayoutParams();
|
||||
params.gravity = Gravity.CENTER;
|
||||
teamNum.setLayoutParams(params);
|
||||
teamNum.setPadding(10,10,10,10);
|
||||
teamNum.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline6);
|
||||
teamNum.setText(String.valueOf(view.teamNum));
|
||||
|
||||
|
||||
row.addView(teamNum);
|
||||
row.addView(view);
|
||||
|
||||
parent.addView(row);
|
||||
}
|
||||
}
|
||||
|
||||
public String toString(DataType data){
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TableLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.ridgebotics.ridgescout.types.data.DataType;
|
||||
@@ -25,6 +26,7 @@ import com.github.mikephil.charting.data.LineDataSet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class TextType extends FieldType {
|
||||
@@ -221,7 +223,7 @@ public class TextType extends FieldType {
|
||||
|
||||
}
|
||||
|
||||
public void addDataToTable(LinearLayout parent, List<DataType>[] data){
|
||||
public void addDataToTable(TableLayout parent, Map<Integer, List<DataType>> data){
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.ridgebotics.ridgescout.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
public class CandlestickHeader extends View {
|
||||
|
||||
private float absoluteMin = 0;
|
||||
private float absoluteMax = 100;
|
||||
|
||||
// Number of scale marks to show
|
||||
private final int SCALE_MARKS = 5;
|
||||
|
||||
// Padding and dimensions
|
||||
private final int PADDING_LEFT = 50;
|
||||
private final int PADDING_RIGHT = 50;
|
||||
private final int PADDING_TOP = 10;
|
||||
private final int PADDING_BOTTOM = 10;
|
||||
private final int TICK_HEIGHT = 10;
|
||||
|
||||
private Paint linePaint;
|
||||
private Paint textPaint;
|
||||
|
||||
public CandlestickHeader(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public CandlestickHeader(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public CandlestickHeader(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
linePaint.setColor(Color.GREEN);
|
||||
linePaint.setStrokeWidth(2f);
|
||||
linePaint.setStyle(Paint.Style.STROKE);
|
||||
|
||||
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
textPaint.setColor(Color.GREEN);
|
||||
textPaint.setTextSize(30f);
|
||||
textPaint.setTextAlign(Paint.Align.CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the scale range for the header
|
||||
*
|
||||
* @param absoluteMin Absolute minimum of the dataset
|
||||
* @param absoluteMax Absolute maximum of the dataset
|
||||
*/
|
||||
public void setScale(float absoluteMin, float absoluteMax) {
|
||||
this.absoluteMin = absoluteMin;
|
||||
this.absoluteMax = absoluteMax;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int desiredWidth = 600;
|
||||
int desiredHeight = 80; // Height for the scale with labels
|
||||
|
||||
int width = resolveSize(desiredWidth, widthMeasureSpec);
|
||||
int height = resolveSize(desiredHeight, heightMeasureSpec);
|
||||
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
|
||||
// Draw horizontal axis
|
||||
float y = height - PADDING_BOTTOM;
|
||||
canvas.drawLine(PADDING_LEFT, y, width - PADDING_RIGHT, y, linePaint);
|
||||
|
||||
// Calculate interval between scale marks
|
||||
float availableWidth = width - PADDING_LEFT - PADDING_RIGHT;
|
||||
float intervalValue = (absoluteMax - absoluteMin) / (SCALE_MARKS - 1);
|
||||
float intervalPixels = availableWidth / (SCALE_MARKS - 1);
|
||||
|
||||
// Draw scale marks and labels
|
||||
for (int i = 0; i < SCALE_MARKS; i++) {
|
||||
float x = PADDING_LEFT + (intervalPixels * i);
|
||||
float value = absoluteMin + (intervalValue * i);
|
||||
|
||||
// Draw tick mark
|
||||
canvas.drawLine(x, y, x, y - TICK_HEIGHT, linePaint);
|
||||
|
||||
// Draw label
|
||||
canvas.drawText(String.format("%.1f", value), x, y - TICK_HEIGHT - 10, textPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package com.ridgebotics.ridgescout.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import com.ridgebotics.ridgescout.types.data.DataType;
|
||||
import com.ridgebotics.ridgescout.ui.data.DataProcessing;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CandlestickView extends View {
|
||||
|
||||
// Data points
|
||||
private float min = 0;
|
||||
private float max = 0;
|
||||
private float lowerQuartile = 0;
|
||||
private float upperQuartile = 0;
|
||||
public float average = 0;
|
||||
|
||||
// Dataset absolute bounds for scaling
|
||||
private float absoluteMin = 0;
|
||||
private float absoluteMax = 100;
|
||||
|
||||
// Padding and dimensions
|
||||
private final int PADDING_LEFT = 50;
|
||||
private final int PADDING_RIGHT = 50;
|
||||
private final int PADDING_TOP = 20;
|
||||
private final int PADDING_BOTTOM = 20;
|
||||
private final int CANDLESTICK_HEIGHT = 60;
|
||||
private final int WHISKER_HEIGHT = 10;
|
||||
|
||||
// Paint objects
|
||||
private Paint boxPaint;
|
||||
private Paint whiskerPaint;
|
||||
private Paint averagePaint;
|
||||
private Paint textPaint;
|
||||
|
||||
public CandlestickView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public CandlestickView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public CandlestickView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
// Initialize paint objects
|
||||
boxPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
boxPaint.setColor(Color.GREEN);
|
||||
boxPaint.setStyle(Paint.Style.STROKE);
|
||||
boxPaint.setStrokeWidth(2f);
|
||||
|
||||
whiskerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
whiskerPaint.setColor(Color.GREEN);
|
||||
whiskerPaint.setStrokeWidth(2f);
|
||||
whiskerPaint.setStyle(Paint.Style.STROKE);
|
||||
|
||||
averagePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
averagePaint.setColor(Color.GREEN);
|
||||
averagePaint.setStrokeWidth(3f);
|
||||
|
||||
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
textPaint.setColor(Color.GREEN);
|
||||
textPaint.setTextSize(30f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set data for the candlestick chart
|
||||
*
|
||||
* @param min Minimum value
|
||||
* @param lowerQuartile Lower quartile value
|
||||
* @param average Average value
|
||||
* @param upperQuartile Upper quartile value
|
||||
* @param max Maximum value
|
||||
* @param absoluteMin Absolute minimum of the dataset
|
||||
* @param absoluteMax Absolute maximum of the dataset
|
||||
*/
|
||||
public void setData(float min, float lowerQuartile, float average,
|
||||
float upperQuartile, float max,
|
||||
float absoluteMin, float absoluteMax) {
|
||||
this.min = min;
|
||||
this.lowerQuartile = lowerQuartile;
|
||||
this.average = average;
|
||||
this.upperQuartile = upperQuartile;
|
||||
this.max = max;
|
||||
this.absoluteMin = absoluteMin;
|
||||
this.absoluteMax = absoluteMax;
|
||||
|
||||
// Request redraw
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int desiredWidth = 600;
|
||||
int desiredHeight = CANDLESTICK_HEIGHT + PADDING_TOP + PADDING_BOTTOM;
|
||||
|
||||
int width = resolveSize(desiredWidth, widthMeasureSpec);
|
||||
int height = resolveSize(desiredHeight, heightMeasureSpec);
|
||||
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
|
||||
// Calculate scaling factor for data points
|
||||
float availableWidth = width - PADDING_LEFT - PADDING_RIGHT;
|
||||
float scale = availableWidth / (absoluteMax - absoluteMin);
|
||||
|
||||
// Calculate vertical center of the view
|
||||
int centerY = height / 2;
|
||||
|
||||
// Calculate scaled positions
|
||||
float scaledMin = PADDING_LEFT + (min - absoluteMin) * scale;
|
||||
float scaledLQ = PADDING_LEFT + (lowerQuartile - absoluteMin) * scale;
|
||||
float scaledAvg = PADDING_LEFT + (average - absoluteMin) * scale;
|
||||
float scaledUQ = PADDING_LEFT + (upperQuartile - absoluteMin) * scale;
|
||||
float scaledMax = PADDING_LEFT + (max - absoluteMin) * scale;
|
||||
|
||||
// Draw the box (interquartile range)
|
||||
RectF box = new RectF(
|
||||
scaledLQ,
|
||||
centerY - CANDLESTICK_HEIGHT / 2,
|
||||
scaledUQ,
|
||||
centerY + CANDLESTICK_HEIGHT / 2);
|
||||
canvas.drawRect(box, boxPaint);
|
||||
|
||||
// Draw whiskers (min to lower quartile and upper quartile to max)
|
||||
// Left whisker
|
||||
canvas.drawLine(scaledMin, centerY, scaledLQ, centerY, whiskerPaint);
|
||||
canvas.drawLine(
|
||||
scaledMin,
|
||||
centerY - WHISKER_HEIGHT,
|
||||
scaledMin,
|
||||
centerY + WHISKER_HEIGHT,
|
||||
whiskerPaint);
|
||||
|
||||
// Right whisker
|
||||
canvas.drawLine(scaledUQ, centerY, scaledMax, centerY, whiskerPaint);
|
||||
canvas.drawLine(
|
||||
scaledMax,
|
||||
centerY - WHISKER_HEIGHT,
|
||||
scaledMax,
|
||||
centerY + WHISKER_HEIGHT,
|
||||
whiskerPaint);
|
||||
|
||||
// Draw average line
|
||||
canvas.drawLine(
|
||||
scaledAvg,
|
||||
centerY - CANDLESTICK_HEIGHT / 2,
|
||||
scaledAvg,
|
||||
centerY + CANDLESTICK_HEIGHT / 2,
|
||||
averagePaint);
|
||||
|
||||
// Draw labels
|
||||
// canvas.drawText(String.format("%.1f", min), scaledMin - 20, centerY - CANDLESTICK_HEIGHT / 2 - 10, textPaint);
|
||||
// canvas.drawText(String.format("%.1f", max), scaledMax - 20, centerY - CANDLESTICK_HEIGHT / 2 - 10, textPaint);
|
||||
// canvas.drawText(String.format("%.1f", average), scaledAvg - 20, centerY + CANDLESTICK_HEIGHT / 2 + 30, textPaint);
|
||||
}
|
||||
|
||||
public int teamNum;
|
||||
|
||||
public void fromTeamData(List<DataType> teamData, Integer teamNum, float absmin, float absmax){
|
||||
this.teamNum = teamNum;
|
||||
int[] tmp_loc_bounds = DataProcessing.getNumberBounds(teamData);
|
||||
int locmin = tmp_loc_bounds[0];
|
||||
int locmax = tmp_loc_bounds[1];
|
||||
|
||||
float avg = 0;
|
||||
|
||||
for(int i = 0; i < teamData.size(); i++){
|
||||
avg += (int) teamData.get(i).get();
|
||||
}
|
||||
|
||||
avg /= teamData.size();
|
||||
|
||||
float[] teamDataArray = new float[teamData.size()];
|
||||
for(int i = 0; i < teamData.size(); i++){
|
||||
teamDataArray[i] = (int) teamData.get(i).get();
|
||||
}
|
||||
|
||||
float lowerQuartile = DataProcessing.calculatePercentile(teamDataArray, 25);
|
||||
float upperQuartile = DataProcessing.calculatePercentile(teamDataArray, 75);
|
||||
|
||||
System.out.println(locmin + ", " + lowerQuartile + ", " + avg + ", " + upperQuartile + ", " + locmax);
|
||||
setData(locmin, lowerQuartile, avg, upperQuartile, locmax, absmin, absmax);
|
||||
}
|
||||
}
|
||||
@@ -83,6 +83,9 @@ public class DataFragment extends Fragment {
|
||||
|
||||
public void load_teams(){
|
||||
DataManager.reload_event();
|
||||
|
||||
if(event == null) return;
|
||||
|
||||
int[] teamNums = new int[event.teams.size()];
|
||||
|
||||
for(int i = 0 ; i < event.teams.size(); i++){
|
||||
@@ -115,6 +118,8 @@ public class DataFragment extends Fragment {
|
||||
public void load_fields(){
|
||||
DataManager.reload_match_fields();
|
||||
|
||||
if(match_latest_values == null) return;
|
||||
|
||||
for(int i = 0; i < match_latest_values.length; i++){
|
||||
FieldBorderedRow tr = new FieldBorderedRow(getContext());
|
||||
tr.fromField(match_latest_values[i]);
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.ridgebotics.ridgescout.ui.data;
|
||||
|
||||
import com.ridgebotics.ridgescout.types.data.DataType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class DataProcessing {
|
||||
public static int[] getNumberBounds(Map<Integer, List<DataType>> data){
|
||||
int min = Integer.MAX_VALUE;
|
||||
int max = Integer.MIN_VALUE;
|
||||
|
||||
|
||||
|
||||
for(Integer teamNum : data.keySet()){
|
||||
List<DataType> teamData = data.get(teamNum);
|
||||
|
||||
int[] locBounds = getNumberBounds(teamData);
|
||||
|
||||
if(locBounds[1] > max) max = locBounds[1];
|
||||
if(locBounds[0] < min) min = locBounds[0];
|
||||
|
||||
}
|
||||
|
||||
return new int[]{min, max};
|
||||
}
|
||||
|
||||
public static int[] getNumberBounds(List<DataType> data){
|
||||
int min = Integer.MAX_VALUE;
|
||||
int max = Integer.MIN_VALUE;
|
||||
|
||||
if(data == null) return new int[]{min, max};
|
||||
for(int i = 0; i < data.size(); i++){
|
||||
DataType dataPoint = data.get(i);
|
||||
// if(dataPoint == null) continue;
|
||||
int num = (int) dataPoint.get();
|
||||
if(num > max) max = num;
|
||||
if(num < min) min = num;
|
||||
}
|
||||
|
||||
return new int[]{min, max};
|
||||
}
|
||||
|
||||
|
||||
//https://stackoverflow.com/questions/42381759/finding-first-quartile-and-third-quartile-in-integer-array-using-java#63891545
|
||||
public static float[] getQuartiles(List<DataType> data) {
|
||||
float ans[] = new float[3];
|
||||
|
||||
float[] val = new float[data.size()];
|
||||
for(int i = 0; i < val.length; i++){
|
||||
val[i] = (int) data.get(i).get();
|
||||
}
|
||||
|
||||
for (int quartileType = 1; quartileType < 4; quartileType++) {
|
||||
float length = val.length + 1;
|
||||
float quartile;
|
||||
float newArraySize = (length * ((float) (quartileType) * 25 / 100)) - 1;
|
||||
Arrays.sort(val);
|
||||
if (newArraySize % 1 == 0) {
|
||||
quartile = val[(int) (newArraySize)];
|
||||
} else {
|
||||
int newArraySize1 = (int) (newArraySize);
|
||||
quartile = (val[newArraySize1] + val[newArraySize1 + 1]) / 2;
|
||||
}
|
||||
ans[quartileType - 1] = quartile;
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the specified percentile of a dataset.
|
||||
*
|
||||
* @param data The dataset to analyze
|
||||
* @param percentile The percentile to find (0-100)
|
||||
* @return The value at the specified percentile
|
||||
*/
|
||||
public static float calculatePercentile(float[] data, float percentile) {
|
||||
// Make a copy of the data to avoid modifying the original
|
||||
float[] dataCopy = Arrays.copyOf(data, data.length);
|
||||
|
||||
// Sort the data in ascending order
|
||||
Arrays.sort(dataCopy);
|
||||
|
||||
// Calculate the position as a fraction
|
||||
float position = (percentile / 100.0f) * (dataCopy.length - 1);
|
||||
|
||||
// Get the integer part of the position
|
||||
int lowerIndex = (int) Math.floor(position);
|
||||
int upperIndex = (int) Math.ceil(position);
|
||||
|
||||
// If position is already an integer, return the value at that position
|
||||
if (lowerIndex == upperIndex) {
|
||||
return dataCopy[lowerIndex];
|
||||
}
|
||||
|
||||
// Otherwise, interpolate between the two adjacent values
|
||||
float fraction = position - lowerIndex;
|
||||
float lowerValue = dataCopy[lowerIndex];
|
||||
float upperValue = dataCopy[upperIndex];
|
||||
|
||||
// Linear interpolation
|
||||
return lowerValue + fraction * (upperValue - lowerValue);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,7 +25,9 @@ import com.ridgebotics.ridgescout.utility.AlertManager;
|
||||
import com.ridgebotics.ridgescout.utility.FileEditor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class FieldDataFragment extends Fragment {
|
||||
|
||||
@@ -50,22 +52,25 @@ public class FieldDataFragment extends Fragment {
|
||||
|
||||
Thread t = new Thread(() -> {
|
||||
|
||||
List<DataType>[] data = new ArrayList[event.teams.size()];
|
||||
Map<Integer, List<DataType>> data = new HashMap<>();
|
||||
for (int teamIndex = 0; teamIndex < event.teams.size(); teamIndex++) {
|
||||
|
||||
int teamNum = event.teams.get(teamIndex).teamNumber;
|
||||
List<String> filenames = new ArrayList<>(List.of(FileEditor.getMatchesByTeamNum(evcode, event.teams.get(teamIndex).teamNumber)));
|
||||
filenames.removeAll(rescout_list);
|
||||
|
||||
ArrayList<DataType> teamData = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < filenames.size(); i++) {
|
||||
data[teamIndex] = new ArrayList<>();
|
||||
try {
|
||||
ScoutingDataWriter.ParsedScoutingDataResult psda = ScoutingDataWriter.load(filenames.get(i), match_values, match_transferValues);
|
||||
if (psda.data.array[fieldIndex] != null && psda.data.array[fieldIndex].get() != null)
|
||||
data[teamIndex].add(psda.data.array[fieldIndex]);
|
||||
teamData.add(psda.data.array[fieldIndex]);
|
||||
} catch (Exception e) {
|
||||
AlertManager.error("Failure to load file " + filenames.get(i), e);
|
||||
}
|
||||
}
|
||||
|
||||
data.put(teamNum, teamData);
|
||||
}
|
||||
|
||||
System.out.println("Finished!");
|
||||
@@ -73,6 +78,7 @@ public class FieldDataFragment extends Fragment {
|
||||
|
||||
|
||||
getActivity().runOnUiThread(() -> {
|
||||
binding.table.setStretchAllColumns(false);
|
||||
match_latest_values[fieldIndex].addDataToTable(binding.table, data);
|
||||
stopLoading();
|
||||
});
|
||||
|
||||
@@ -62,18 +62,18 @@ public class AlertManager {
|
||||
StringWriter sw = new StringWriter();
|
||||
e.printStackTrace(new PrintWriter(sw));
|
||||
|
||||
errorList.add((sw.toString()));
|
||||
errorList.add(sw.getBuffer().toString());
|
||||
updateErrors();
|
||||
}
|
||||
|
||||
public static void error(String title, Exception e) {
|
||||
simpleErrorList.add(title);
|
||||
e.printStackTrace();
|
||||
|
||||
StringWriter sw = new StringWriter();
|
||||
e.printStackTrace(new PrintWriter(sw));
|
||||
|
||||
errorList.add((sw.toString()));
|
||||
errorList.add(sw.toString());
|
||||
e.printStackTrace();
|
||||
updateErrors();
|
||||
}
|
||||
|
||||
@@ -97,16 +97,15 @@ public class AlertManager {
|
||||
|
||||
alert.setPositiveButton("OK", (dialogInterface, i) -> {if(currentError != null){errorList.clear(); simpleErrorList.clear();}});
|
||||
|
||||
String detailedErrors = String.join("\n\n\n\n\n", errorList);
|
||||
|
||||
if(!errorList.isEmpty())
|
||||
alert.setNeutralButton("View Detailed Error" + (errorList.size() != 1 ? "s" : ""), (dialogInterface, i) -> alert(errorList.size() + " Error" + (errorList.size() != 1 ? "s" : "") + ":", String.join("\n\n\n\n\n", errorList)));
|
||||
alert.setNeutralButton("View Detailed Error" + (errorList.size() != 1 ? "s" : ""), (dialogInterface, i) -> alert("Details", detailedErrors));
|
||||
|
||||
alert.setOnDismissListener((x) -> {if(currentError != null){errorList.clear(); simpleErrorList.clear();}});
|
||||
|
||||
|
||||
alert.setCancelable(true);
|
||||
|
||||
currentError = alert.create();
|
||||
|
||||
currentError.show();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user