package nodomain.freeyourgadget.gadgetbridge.activities.dashboard; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.util.TypedValue; import android.widget.ImageView; import androidx.annotation.ColorInt; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; public class GaugeDrawer { private static final Logger LOG = LoggerFactory.getLogger(GaugeDrawer.class); protected @ColorInt int color_unknown = Color.argb(25, 128, 128, 128); /** * Draw a simple gauge. * * @param color the gauge color * @param value the gauge value. Range: [0, 1] */ public void drawSimpleGauge(ImageView gaugeBar, final int color, final float value) { final int width = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 150, GBApplication.getContext().getResources().getDisplayMetrics() ); // Draw gauge gaugeBar.setImageBitmap(drawSimpleGaugeInternal( width, Math.round(width * 0.075f), color, value )); } /** * @param width Bitmap width in pixels * @param barWidth Gauge bar width in pixels * @param filledColor Color of the filled part of the gauge * @param filledFactor Factor between 0 and 1 that determines the amount of the gauge that should be filled * @return Bitmap containing the gauge */ private Bitmap drawSimpleGaugeInternal(final int width, final int barWidth, @ColorInt final int filledColor, final float filledFactor) { final int height = width / 2; final int barMargin = (int) Math.ceil(barWidth / 2f); final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); final Canvas canvas = new Canvas(bitmap); final Paint paint = new Paint(); paint.setAntiAlias(true); paint.setStyle(Paint.Style.STROKE); paint.setStrokeCap(Paint.Cap.ROUND); paint.setStrokeWidth(barWidth * 0.75f); paint.setColor(color_unknown); canvas.drawArc(barMargin, barMargin, width - barMargin, width - barMargin, 180 + 180 * filledFactor, 180 - 180 * filledFactor, false, paint); if (filledFactor >= 0) { paint.setStrokeWidth(barWidth); paint.setColor(filledColor); canvas.drawArc(barMargin, barMargin, width - barMargin, width - barMargin, 180, 180 * filledFactor, false, paint); } return bitmap; } /** * Draws a segmented gauge. * * @param colors the colors of each segment * @param segments the size of each segment. The sum of all segments should be 1 * @param value the gauge value, in range [0, 1], or -1 for no value and only segments * @param fadeOutsideDot whether to fade out colors outside the dot value * @param gapBetweenSegments whether to introduce a small gap between the segments */ public void drawSegmentedGauge(ImageView gaugeBar, final int[] colors, final float[] segments, final float value, final boolean fadeOutsideDot, final boolean gapBetweenSegments) { if (colors.length != segments.length) { LOG.error("Colors length {} differs from segments length {}", colors.length, segments.length); return; } final int width = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 150, GBApplication.getContext().getResources().getDisplayMetrics() ); final int barWidth = Math.round(width * 0.075f); final int height = width / 2; final int barMargin = (int) Math.ceil(barWidth / 2f); final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); final Canvas canvas = new Canvas(bitmap); final Paint paint = new Paint(); paint.setAntiAlias(true); paint.setStyle(Paint.Style.STROKE); paint.setStrokeCap(Paint.Cap.BUTT); paint.setStrokeWidth(barWidth); final double cornersGapRadians = Math.asin((width * 0.055f) / (double) height); final double cornersGapFactor = cornersGapRadians / Math.PI; int dotColor = 0; float angleSum = 0; for (int i = 0; i < segments.length; i++) { if (segments[i] == 0) { continue; } paint.setColor(colors[i]); paint.setStrokeWidth(barWidth); if (value < 0 || (value >= angleSum && value <= angleSum + segments[i])) { dotColor = colors[i]; } else { if (fadeOutsideDot) { paint.setColor(colors[i] - 0xB0000000); } else { paint.setStrokeWidth(barWidth * 0.75f); } } float startAngleDegrees = 180 + angleSum * 180; float sweepAngleDegrees = segments[i] * 180; if (value >= 0) { // Do not draw to the end if it will be overlapped by the dot if (i == 0 && value <= cornersGapFactor) { startAngleDegrees += (float) Math.toDegrees(cornersGapRadians); sweepAngleDegrees -= (float) Math.toDegrees(cornersGapRadians); } else if (i == segments.length - 1 && value >= 1 - cornersGapFactor) { sweepAngleDegrees -= (float) Math.toDegrees(cornersGapRadians); } } if (gapBetweenSegments) { if (i + 1 < segments.length) { sweepAngleDegrees -= 2; } } canvas.drawArc( barMargin, barMargin, width - barMargin, width - barMargin, startAngleDegrees, sweepAngleDegrees, false, paint ); angleSum += segments[i]; } if (value >= 0) { // Prevent the dot from going outside the widget in the extremities final float angleRadians = (float) normalize(value, 0, 1, cornersGapRadians, Math.toRadians(180) - cornersGapRadians); paint.setColor(Color.TRANSPARENT); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); // In the corners the circle is slightly offset, so adjust it slightly final float widthAdjustment = width * 0.04f * (float) normalize(Math.abs(value - 0.5d), 0, 0.5d); final float x = ((width - (barWidth / 2f) - widthAdjustment) / 2f) * (float) Math.cos(angleRadians); final float y = (height - (barWidth / 2f)) * (float) Math.sin(angleRadians); // Draw hole paint.setStyle(Paint.Style.FILL); canvas.drawCircle((width / 2f) - x, height - y, barMargin * 1.6f, paint); // Draw dot paint.setColor(dotColor); paint.setXfermode(null); canvas.drawCircle((width / 2f) - x, height - y, barMargin, paint); } gaugeBar.setImageBitmap(bitmap); } public static Bitmap drawCircleGaugeSegmented(int width, int barWidth, final int[] colors, final float[] segments, final boolean gapBetweenSegments, String text, String lowerText, Context context) { int TEXT_COLOR = GBApplication.getTextColor(context); int height = width; int barMargin = (int) Math.ceil(barWidth / 2f); Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); Paint paint = new Paint(); paint.setAntiAlias(true); paint.setStyle(Paint.Style.STROKE); paint.setStrokeCap(Paint.Cap.BUTT); paint.setStrokeWidth(barWidth); paint.setColor(context.getResources().getColor(R.color.gauge_line_color)); canvas.drawArc( barMargin, barMargin, width - barMargin, width - barMargin, 90, 360, false, paint); paint.setStrokeWidth(barWidth); float remainingAngle = 360; float gapDegree = 1f; if (gapBetweenSegments) { int validSegments = segments.length; for (int i = 0; i < segments.length; i++) { if (segments[i] == 0) { validSegments--; } } remainingAngle = 360 - (validSegments * gapDegree); } float angleSum = 0; for (int i = 0; i < segments.length; i++) { if (segments[i] == 0) { continue; } paint.setColor(colors[i]); paint.setStrokeWidth(barWidth); float startAngleDegrees = 270 + (angleSum * remainingAngle); float sweepAngleDegrees = segments[i] * remainingAngle; canvas.drawArc( barMargin, barMargin, width - barMargin, height - barMargin, startAngleDegrees, sweepAngleDegrees, false, paint ); angleSum += segments[i]; if (gapBetweenSegments) { angleSum += (gapDegree / 360f); } } Paint textPaint = new Paint(); textPaint.setColor(TEXT_COLOR); float textPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, width * 0.06f, context.getResources().getDisplayMetrics()); textPaint.setTextSize(textPixels); textPaint.setTextAlign(Paint.Align.CENTER); int yPos = (int) ((float) height / 2 - ((textPaint.descent() + textPaint.ascent()) / 2)) ; canvas.drawText(String.valueOf(text), width / 2f, yPos, textPaint); Paint textLowerPaint = new Paint(); textLowerPaint.setColor(TEXT_COLOR); textLowerPaint.setTextAlign(Paint.Align.CENTER); float textLowerPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, width * 0.025f, context.getResources().getDisplayMetrics()); textLowerPaint.setTextSize(textLowerPixels); int yPosLowerText = (int) ((float) height / 2 - textPaint.ascent()) ; canvas.drawText(String.valueOf(lowerText), width / 2f, yPosLowerText, textLowerPaint); return bitmap; } public static Bitmap drawCircleGauge(int width, int barWidth, @ColorInt int filledColor, int value, int maxValue, Context context) { int TEXT_COLOR = GBApplication.getTextColor(context); int height = width; int barMargin = (int) Math.ceil(barWidth / 2f); float filledFactor = (float) value / maxValue; Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); Paint paint = new Paint(); paint.setAntiAlias(true); paint.setStyle(Paint.Style.STROKE); paint.setStrokeCap(Paint.Cap.ROUND); paint.setStrokeWidth(barWidth); paint.setColor(context.getResources().getColor(R.color.gauge_line_color)); canvas.drawArc( barMargin, barMargin, width - barMargin, width - barMargin, 90, 360, false, paint); paint.setStrokeWidth(barWidth); paint.setColor(filledColor); canvas.drawArc( barMargin, barMargin, width - barMargin, height - barMargin, 270, 360 * filledFactor, false, paint ); Paint textPaint = new Paint(); textPaint.setColor(TEXT_COLOR); float textPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, width * 0.06f, context.getResources().getDisplayMetrics()); textPaint.setTextSize(textPixels); textPaint.setTextAlign(Paint.Align.CENTER); int yPos = (int) ((float) height / 2 - ((textPaint.descent() + textPaint.ascent()) / 2)) ; canvas.drawText(String.valueOf(value), width / 2f, yPos, textPaint); Paint textLowerPaint = new Paint(); textLowerPaint.setColor(TEXT_COLOR); textLowerPaint.setTextAlign(Paint.Align.CENTER); float textLowerPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, width * 0.025f, context.getResources().getDisplayMetrics()); textLowerPaint.setTextSize(textLowerPixels); int yPosLowerText = (int) ((float) height / 2 - textPaint.ascent()) ; canvas.drawText(String.valueOf(maxValue), width / 2f, yPosLowerText, textLowerPaint); return bitmap; } public static double normalize(final double value, final double min, final double max) { return normalize(value, min, max, 0, 1); } public static double normalize(final double value, final double minSource, final double maxSource, final double minTarget, final double maxTarget) { return ((value - minSource) * (maxTarget - minTarget)) / (maxSource - minSource) + minTarget; } }