GraphSeries -> kt
This commit is contained in:
parent
3ba774edc0
commit
83fc6a0490
6 changed files with 1114 additions and 1227 deletions
|
@ -1,453 +0,0 @@
|
||||||
/**
|
|
||||||
* GraphView
|
|
||||||
* Copyright (C) 2014 Jonas Gehring
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License,
|
|
||||||
* with the "Linking Exception", which can be found at the license.txt
|
|
||||||
* file in this program.
|
|
||||||
*
|
|
||||||
* This program 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 General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* with the "Linking Exception" along with this program; if not,
|
|
||||||
* write to the author Jonas Gehring <g.jjoe64@gmail.com>.
|
|
||||||
*/
|
|
||||||
package info.nightscout.core.graph.data;
|
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Path;
|
|
||||||
|
|
||||||
import com.jjoe64.graphview.GraphView;
|
|
||||||
import com.jjoe64.graphview.series.BaseSeries;
|
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Series to plot the data as line.
|
|
||||||
* The line can be styled with many options.
|
|
||||||
*
|
|
||||||
* @author jjoe64
|
|
||||||
*/
|
|
||||||
public class AreaGraphSeries<E extends DoubleDataPoint> extends BaseSeries<E> {
|
|
||||||
/**
|
|
||||||
* wrapped styles regarding the line
|
|
||||||
*/
|
|
||||||
private static final class Styles {
|
|
||||||
/**
|
|
||||||
* the thickness of the line.
|
|
||||||
* This option will be ignored if you are
|
|
||||||
* using a custom paint via {@link #setCustomPaint(Paint)}
|
|
||||||
*/
|
|
||||||
private int thickness = 5;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* flag whether the area under the line to the bottom
|
|
||||||
* of the viewport will be filled with a
|
|
||||||
* specific background color.
|
|
||||||
*
|
|
||||||
* @see #backgroundColor
|
|
||||||
*/
|
|
||||||
private boolean drawBackground = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* flag whether the data points are highlighted as
|
|
||||||
* a visible point.
|
|
||||||
*
|
|
||||||
* @see #dataPointsRadius
|
|
||||||
*/
|
|
||||||
private boolean drawDataPoints = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the radius for the data points.
|
|
||||||
*
|
|
||||||
* @see #drawDataPoints
|
|
||||||
*/
|
|
||||||
private float dataPointsRadius = 10f;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the background color for the filling under
|
|
||||||
* the line.
|
|
||||||
*
|
|
||||||
* @see #drawBackground
|
|
||||||
*/
|
|
||||||
private int backgroundColor = Color.argb(100, 172, 218, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* wrapped styles
|
|
||||||
*/
|
|
||||||
private Styles mStyles;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* internal paint object
|
|
||||||
*/
|
|
||||||
private Paint mPaint;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* paint for the background
|
|
||||||
*/
|
|
||||||
private Paint mPaintBackground;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* path for the background filling
|
|
||||||
*/
|
|
||||||
private Path mPathBackground;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* path to the line
|
|
||||||
*/
|
|
||||||
private Path mPath;
|
|
||||||
private Path mSecondPath;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* custom paint that can be used.
|
|
||||||
* this will ignore the thickness and color styles.
|
|
||||||
*/
|
|
||||||
private Paint mCustomPaint;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* creates a series without data
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused") public AreaGraphSeries() {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* creates a series with data
|
|
||||||
*
|
|
||||||
* @param data data points
|
|
||||||
*/
|
|
||||||
public AreaGraphSeries(E[] data) {
|
|
||||||
super(data);
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* do the initialization
|
|
||||||
* creates internal objects
|
|
||||||
*/
|
|
||||||
protected void init() {
|
|
||||||
mStyles = new Styles();
|
|
||||||
mPaint = new Paint();
|
|
||||||
mPaint.setStrokeCap(Paint.Cap.ROUND);
|
|
||||||
mPaint.setStyle(Paint.Style.STROKE);
|
|
||||||
mPaintBackground = new Paint();
|
|
||||||
|
|
||||||
mPathBackground = new Path();
|
|
||||||
mPath = new Path();
|
|
||||||
mSecondPath = new Path();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* plots the series
|
|
||||||
* draws the line and the background
|
|
||||||
*
|
|
||||||
* @param graphView graphview
|
|
||||||
* @param canvas canvas
|
|
||||||
* @param isSecondScale flag if it is the second scale
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void draw(GraphView graphView, Canvas canvas, boolean isSecondScale) {
|
|
||||||
resetDataPoints();
|
|
||||||
|
|
||||||
// get data
|
|
||||||
double maxX = graphView.getViewport().getMaxX(false);
|
|
||||||
double minX = graphView.getViewport().getMinX(false);
|
|
||||||
|
|
||||||
double maxY;
|
|
||||||
double minY;
|
|
||||||
if (isSecondScale) {
|
|
||||||
maxY = graphView.getSecondScale().getMaxY();
|
|
||||||
minY = graphView.getSecondScale().getMinY();
|
|
||||||
} else {
|
|
||||||
maxY = graphView.getViewport().getMaxY(false);
|
|
||||||
minY = graphView.getViewport().getMinY(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterator<E> values = getValues(minX, maxX);
|
|
||||||
|
|
||||||
// draw background
|
|
||||||
double lastEndY1;
|
|
||||||
double lastEndY2;
|
|
||||||
double lastEndX;
|
|
||||||
|
|
||||||
// draw data
|
|
||||||
mPaint.setStrokeWidth(mStyles.thickness);
|
|
||||||
mPaint.setColor(getColor());
|
|
||||||
mPaintBackground.setColor(mStyles.backgroundColor);
|
|
||||||
|
|
||||||
Paint paint;
|
|
||||||
if (mCustomPaint != null) {
|
|
||||||
paint = mCustomPaint;
|
|
||||||
} else {
|
|
||||||
paint = mPaint;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mStyles.drawBackground) {
|
|
||||||
mPathBackground.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
double diffY = maxY - minY;
|
|
||||||
double diffX = maxX - minX;
|
|
||||||
|
|
||||||
float graphHeight = graphView.getGraphContentHeight();
|
|
||||||
float graphWidth = graphView.getGraphContentWidth();
|
|
||||||
float graphLeft = graphView.getGraphContentLeft();
|
|
||||||
float graphTop = graphView.getGraphContentTop();
|
|
||||||
|
|
||||||
lastEndY1 = 0;
|
|
||||||
lastEndY2 = 0;
|
|
||||||
lastEndX = 0;
|
|
||||||
int i=0;
|
|
||||||
while (values.hasNext()) {
|
|
||||||
E value = values.next();
|
|
||||||
|
|
||||||
double valY1 = value.getY() - minY;
|
|
||||||
double ratY1 = valY1 / diffY;
|
|
||||||
double y1 = graphHeight * ratY1;
|
|
||||||
|
|
||||||
double valY2 = value.getY2() - minY;
|
|
||||||
double ratY2 = valY2 / diffY;
|
|
||||||
double y2 = graphHeight * ratY2;
|
|
||||||
|
|
||||||
double valX = value.getX() - minX;
|
|
||||||
double ratX = valX / diffX;
|
|
||||||
double x = graphWidth * ratX;
|
|
||||||
|
|
||||||
double orgX = x;
|
|
||||||
double orgY1 = y1;
|
|
||||||
double orgY2 = y2;
|
|
||||||
|
|
||||||
if (i > 0) {
|
|
||||||
// overdraw
|
|
||||||
if (x > graphWidth) { // end right
|
|
||||||
double b = ((graphWidth - lastEndX) * (y1 - lastEndY1)/(x - lastEndX));
|
|
||||||
y1 = lastEndY1+b;
|
|
||||||
x = graphWidth;
|
|
||||||
}
|
|
||||||
if (x > graphWidth) { // end right
|
|
||||||
double b = ((graphWidth - lastEndX) * (y2 - lastEndY2)/(x - lastEndX));
|
|
||||||
y2 = lastEndY2+b;
|
|
||||||
x = graphWidth;
|
|
||||||
}
|
|
||||||
if (y1 < 0) { // end bottom
|
|
||||||
double b = ((0 - lastEndY1) * (x - lastEndX)/(y1 - lastEndY1));
|
|
||||||
x = lastEndX+b;
|
|
||||||
y1 = 0;
|
|
||||||
}
|
|
||||||
if (y2 < 0) { // end bottom
|
|
||||||
double b = ((0 - lastEndY2) * (x - lastEndX)/(y2 - lastEndY2));
|
|
||||||
x = lastEndX+b;
|
|
||||||
y2 = 0;
|
|
||||||
}
|
|
||||||
if (y1 > graphHeight) { // end top
|
|
||||||
double b = ((graphHeight - lastEndY1) * (x - lastEndX)/(y1 - lastEndY1));
|
|
||||||
x = lastEndX+b;
|
|
||||||
y1 = graphHeight;
|
|
||||||
}
|
|
||||||
if (y2 > graphHeight) { // end top
|
|
||||||
double b = ((graphHeight - lastEndY2) * (x - lastEndX)/(y2 - lastEndY2));
|
|
||||||
x = lastEndX+b;
|
|
||||||
y2 = graphHeight;
|
|
||||||
}
|
|
||||||
if (lastEndY1 < 0) { // start bottom
|
|
||||||
double b = ((0 - y1) * (x - lastEndX)/(lastEndY1 - y1));
|
|
||||||
lastEndX = x-b;
|
|
||||||
lastEndY1 = 0;
|
|
||||||
}
|
|
||||||
if (lastEndY2 < 0) { // start bottom
|
|
||||||
double b = ((0 - y2) * (x - lastEndX)/(lastEndY2 - y2));
|
|
||||||
lastEndX = x-b;
|
|
||||||
lastEndY2 = 0;
|
|
||||||
}
|
|
||||||
if (lastEndX < 0) { // start left
|
|
||||||
double b = ((0 - x) * (y1 - lastEndY1)/(lastEndX - x));
|
|
||||||
lastEndY1 = y1-b;
|
|
||||||
lastEndX = 0;
|
|
||||||
}
|
|
||||||
if (lastEndX < 0) { // start left
|
|
||||||
double b = ((0 - x) * (y2 - lastEndY2)/(lastEndX - x));
|
|
||||||
lastEndY2 = y2-b;
|
|
||||||
lastEndX = 0;
|
|
||||||
}
|
|
||||||
if (lastEndY1 > graphHeight) { // start top
|
|
||||||
double b = ((graphHeight - y1) * (x - lastEndX)/(lastEndY1 - y1));
|
|
||||||
lastEndX = x-b;
|
|
||||||
lastEndY1 = graphHeight;
|
|
||||||
}
|
|
||||||
if (lastEndY2 > graphHeight) { // start top
|
|
||||||
double b = ((graphHeight - y2) * (x - lastEndX)/(lastEndY2 - y2));
|
|
||||||
lastEndX = x-b;
|
|
||||||
lastEndY2 = graphHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
float startX = (float) lastEndX + (graphLeft + 1);
|
|
||||||
float startY1 = (float) (graphTop - lastEndY1) + graphHeight;
|
|
||||||
float startY2 = (float) (graphTop - lastEndY2) + graphHeight;
|
|
||||||
float endX = (float) x + (graphLeft + 1);
|
|
||||||
float endY1 = (float) (graphTop - y1) + graphHeight;
|
|
||||||
float endY2 = (float) (graphTop - y2) + graphHeight;
|
|
||||||
|
|
||||||
// draw data point
|
|
||||||
if (mStyles.drawDataPoints) {
|
|
||||||
//fix: last value was not drawn. Draw here now the end values
|
|
||||||
canvas.drawCircle(endX, endY1, mStyles.dataPointsRadius, mPaint);
|
|
||||||
canvas.drawCircle(endX, endY2, mStyles.dataPointsRadius, mPaint);
|
|
||||||
}
|
|
||||||
registerDataPoint(endX, endY1, value);
|
|
||||||
registerDataPoint(endX, endY2, value);
|
|
||||||
|
|
||||||
mPath.reset();
|
|
||||||
mSecondPath.reset();
|
|
||||||
mPath.moveTo(startX, startY1);
|
|
||||||
mSecondPath.moveTo(startX, startY2);
|
|
||||||
mPath.lineTo(endX, endY1);
|
|
||||||
mSecondPath.lineTo(endX, endY2);
|
|
||||||
canvas.drawPath(mPath, paint);
|
|
||||||
canvas.drawPath(mSecondPath, paint);
|
|
||||||
if (mStyles.drawBackground) {
|
|
||||||
canvas.drawRect(startX, startY2, endX, endY1, mPaintBackground);
|
|
||||||
}
|
|
||||||
} else if (mStyles.drawDataPoints) {
|
|
||||||
//fix: last value not drawn as datapoint. Draw first point here, and then on every step the end values (above)
|
|
||||||
//float first_X = (float) x + (graphLeft + 1);
|
|
||||||
//float first_Y = (float) (graphTop - y) + graphHeight;
|
|
||||||
// canvas.drawCircle(first_X, first_Y, dataPointsRadius, mPaint);
|
|
||||||
}
|
|
||||||
lastEndY1 = orgY1;
|
|
||||||
lastEndY2 = orgY2;
|
|
||||||
lastEndX = orgX;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
if (mStyles.drawBackground) {
|
|
||||||
// end / close path
|
|
||||||
mPathBackground.lineTo((float) lastUsedEndX, graphHeight + graphTop);
|
|
||||||
mPathBackground.lineTo(firstX, graphHeight + graphTop);
|
|
||||||
mPathBackground.close();
|
|
||||||
canvas.drawPath(mPathBackground, mPaintBackground);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the thickness of the line.
|
|
||||||
* This option will be ignored if you are
|
|
||||||
* using a custom paint via {@link #setCustomPaint(Paint)}
|
|
||||||
*
|
|
||||||
* @return the thickness of the line
|
|
||||||
*/
|
|
||||||
public int getThickness() {
|
|
||||||
return mStyles.thickness;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the thickness of the line.
|
|
||||||
* This option will be ignored if you are
|
|
||||||
* using a custom paint via {@link #setCustomPaint(Paint)}
|
|
||||||
*
|
|
||||||
* @param thickness thickness of the line
|
|
||||||
*/
|
|
||||||
public void setThickness(int thickness) {
|
|
||||||
mStyles.thickness = thickness;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* flag whether the area under the line to the bottom
|
|
||||||
* of the viewport will be filled with a
|
|
||||||
* specific background color.
|
|
||||||
*
|
|
||||||
* @return whether the background will be drawn
|
|
||||||
* @see #getBackgroundColor()
|
|
||||||
*/
|
|
||||||
public boolean isDrawBackground() {
|
|
||||||
return mStyles.drawBackground;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* flag whether the area under the line to the bottom
|
|
||||||
* of the viewport will be filled with a
|
|
||||||
* specific background color.
|
|
||||||
*
|
|
||||||
* @param drawBackground whether the background will be drawn
|
|
||||||
* @see #setBackgroundColor(int)
|
|
||||||
*/
|
|
||||||
public void setDrawBackground(boolean drawBackground) {
|
|
||||||
mStyles.drawBackground = drawBackground;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* flag whether the data points are highlighted as
|
|
||||||
* a visible point.
|
|
||||||
*
|
|
||||||
* @return flag whether the data points are highlighted
|
|
||||||
* @see #setDataPointsRadius(float)
|
|
||||||
*/
|
|
||||||
public boolean isDrawDataPoints() {
|
|
||||||
return mStyles.drawDataPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* flag whether the data points are highlighted as
|
|
||||||
* a visible point.
|
|
||||||
*
|
|
||||||
* @param drawDataPoints flag whether the data points are highlighted
|
|
||||||
* @see #setDataPointsRadius(float)
|
|
||||||
*/
|
|
||||||
public void setDrawDataPoints(boolean drawDataPoints) {
|
|
||||||
mStyles.drawDataPoints = drawDataPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the radius for the data points.
|
|
||||||
* @see #setDrawDataPoints(boolean)
|
|
||||||
*/
|
|
||||||
public float getDataPointsRadius() {
|
|
||||||
return mStyles.dataPointsRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param dataPointsRadius the radius for the data points.
|
|
||||||
* @see #setDrawDataPoints(boolean)
|
|
||||||
*/
|
|
||||||
public void setDataPointsRadius(float dataPointsRadius) {
|
|
||||||
mStyles.dataPointsRadius = dataPointsRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the background color for the filling under
|
|
||||||
* the line.
|
|
||||||
* @see #setDrawBackground(boolean)
|
|
||||||
*/
|
|
||||||
public int getBackgroundColor() {
|
|
||||||
return mStyles.backgroundColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param backgroundColor the background color for the filling under
|
|
||||||
* the line.
|
|
||||||
* @see #setDrawBackground(boolean)
|
|
||||||
*/
|
|
||||||
public void setBackgroundColor(int backgroundColor) {
|
|
||||||
mStyles.backgroundColor = backgroundColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* custom paint that can be used.
|
|
||||||
* this will ignore the thickness and color styles.
|
|
||||||
*
|
|
||||||
* @param customPaint the custom paint to be used for rendering the line
|
|
||||||
*/
|
|
||||||
public void setCustomPaint(Paint customPaint) {
|
|
||||||
this.mCustomPaint = customPaint;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,412 @@
|
||||||
|
/**
|
||||||
|
* GraphView
|
||||||
|
* Copyright (C) 2014 Jonas Gehring
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License,
|
||||||
|
* with the "Linking Exception", which can be found at the license.txt
|
||||||
|
* file in this program.
|
||||||
|
*
|
||||||
|
* This program 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* with the "Linking Exception" along with this program; if not,
|
||||||
|
* write to the author Jonas Gehring <g.jjoe64></g.jjoe64>@gmail.com>.
|
||||||
|
*/
|
||||||
|
package info.nightscout.core.graph.data
|
||||||
|
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.Path
|
||||||
|
import com.jjoe64.graphview.GraphView
|
||||||
|
import com.jjoe64.graphview.series.BaseSeries
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Series to plot the data as line.
|
||||||
|
* The line can be styled with many options.
|
||||||
|
*
|
||||||
|
* @author jjoe64
|
||||||
|
*/
|
||||||
|
@Suppress("unused") class AreaGraphSeries<E : DoubleDataPoint?> : BaseSeries<E> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wrapped styles regarding the line
|
||||||
|
*/
|
||||||
|
private class Styles {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the thickness of the line.
|
||||||
|
* This option will be ignored if you are
|
||||||
|
* using a custom paint via [.setCustomPaint]
|
||||||
|
*/
|
||||||
|
var thickness = 5
|
||||||
|
|
||||||
|
/**
|
||||||
|
* flag whether the area under the line to the bottom
|
||||||
|
* of the viewport will be filled with a
|
||||||
|
* specific background color.
|
||||||
|
*
|
||||||
|
* @see .backgroundColor
|
||||||
|
*/
|
||||||
|
var drawBackground = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* flag whether the data points are highlighted as
|
||||||
|
* a visible point.
|
||||||
|
*
|
||||||
|
* @see .dataPointsRadius
|
||||||
|
*/
|
||||||
|
var drawDataPoints = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the radius for the data points.
|
||||||
|
*
|
||||||
|
* @see .drawDataPoints
|
||||||
|
*/
|
||||||
|
var dataPointsRadius = 10f
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the background color for the filling under
|
||||||
|
* the line.
|
||||||
|
*
|
||||||
|
* @see .drawBackground
|
||||||
|
*/
|
||||||
|
var backgroundColor = Color.argb(100, 172, 218, 255)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wrapped styles
|
||||||
|
*/
|
||||||
|
private lateinit var mStyles: Styles
|
||||||
|
|
||||||
|
/**
|
||||||
|
* internal paint object
|
||||||
|
*/
|
||||||
|
private lateinit var mPaint: Paint
|
||||||
|
|
||||||
|
/**
|
||||||
|
* paint for the background
|
||||||
|
*/
|
||||||
|
private lateinit var mPaintBackground: Paint
|
||||||
|
|
||||||
|
/**
|
||||||
|
* path for the background filling
|
||||||
|
*/
|
||||||
|
private lateinit var mPathBackground: Path
|
||||||
|
|
||||||
|
/**
|
||||||
|
* path to the line
|
||||||
|
*/
|
||||||
|
private lateinit var mPath: Path
|
||||||
|
private lateinit var mSecondPath: Path
|
||||||
|
|
||||||
|
/**
|
||||||
|
* custom paint that can be used.
|
||||||
|
* this will ignore the thickness and color styles.
|
||||||
|
*/
|
||||||
|
private var mCustomPaint: Paint? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates a series without data
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
constructor() {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates a series with data
|
||||||
|
*
|
||||||
|
* @param data data points
|
||||||
|
*/
|
||||||
|
constructor(data: Array<E>?) : super(data) {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* do the initialization
|
||||||
|
* creates internal objects
|
||||||
|
*/
|
||||||
|
private fun init() {
|
||||||
|
mStyles = Styles()
|
||||||
|
mPaint = Paint()
|
||||||
|
mPaint.strokeCap = Paint.Cap.ROUND
|
||||||
|
mPaint.style = Paint.Style.STROKE
|
||||||
|
mPaintBackground = Paint()
|
||||||
|
mPathBackground = Path()
|
||||||
|
mPath = Path()
|
||||||
|
mSecondPath = Path()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* plots the series
|
||||||
|
* draws the line and the background
|
||||||
|
*
|
||||||
|
* @param graphView graphview
|
||||||
|
* @param canvas canvas
|
||||||
|
* @param isSecondScale flag if it is the second scale
|
||||||
|
*/
|
||||||
|
override fun draw(graphView: GraphView, canvas: Canvas, isSecondScale: Boolean) {
|
||||||
|
resetDataPoints()
|
||||||
|
|
||||||
|
// get data
|
||||||
|
val maxX = graphView.viewport.getMaxX(false)
|
||||||
|
val minX = graphView.viewport.getMinX(false)
|
||||||
|
val maxY: Double
|
||||||
|
val minY: Double
|
||||||
|
if (isSecondScale) {
|
||||||
|
maxY = graphView.secondScale.maxY
|
||||||
|
minY = graphView.secondScale.minY
|
||||||
|
} else {
|
||||||
|
maxY = graphView.viewport.getMaxY(false)
|
||||||
|
minY = graphView.viewport.getMinY(false)
|
||||||
|
}
|
||||||
|
val values = getValues(minX, maxX)
|
||||||
|
|
||||||
|
// draw background
|
||||||
|
var lastEndY1: Double
|
||||||
|
var lastEndY2: Double
|
||||||
|
var lastEndX: Double
|
||||||
|
|
||||||
|
// draw data
|
||||||
|
mPaint.strokeWidth = mStyles.thickness.toFloat()
|
||||||
|
mPaint.color = color
|
||||||
|
mPaintBackground.color = mStyles.backgroundColor
|
||||||
|
val paint = mCustomPaint ?: mPaint
|
||||||
|
if (mStyles.drawBackground) {
|
||||||
|
mPathBackground.reset()
|
||||||
|
}
|
||||||
|
val diffY = maxY - minY
|
||||||
|
val diffX = maxX - minX
|
||||||
|
val graphHeight = graphView.graphContentHeight.toFloat()
|
||||||
|
val graphWidth = graphView.graphContentWidth.toFloat()
|
||||||
|
val graphLeft = graphView.graphContentLeft.toFloat()
|
||||||
|
val graphTop = graphView.graphContentTop.toFloat()
|
||||||
|
lastEndY1 = 0.0
|
||||||
|
lastEndY2 = 0.0
|
||||||
|
lastEndX = 0.0
|
||||||
|
var i = 0
|
||||||
|
while (values.hasNext()) {
|
||||||
|
val value = values.next() ?: break
|
||||||
|
val valY1 = value.y - minY
|
||||||
|
val ratY1 = valY1 / diffY
|
||||||
|
var y1 = graphHeight * ratY1
|
||||||
|
val valY2 = value.y2 - minY
|
||||||
|
val ratY2 = valY2 / diffY
|
||||||
|
var y2 = graphHeight * ratY2
|
||||||
|
val valX = value.x - minX
|
||||||
|
val ratX = valX / diffX
|
||||||
|
var x = graphWidth * ratX
|
||||||
|
val orgX = x
|
||||||
|
val orgY1 = y1
|
||||||
|
val orgY2 = y2
|
||||||
|
@Suppress("ControlFlowWithEmptyBody")
|
||||||
|
if (i > 0) {
|
||||||
|
// overdraw
|
||||||
|
if (x > graphWidth) { // end right
|
||||||
|
val b = (graphWidth - lastEndX) * (y1 - lastEndY1) / (x - lastEndX)
|
||||||
|
y1 = lastEndY1 + b
|
||||||
|
x = graphWidth.toDouble()
|
||||||
|
}
|
||||||
|
if (x > graphWidth) { // end right
|
||||||
|
val b = (graphWidth - lastEndX) * (y2 - lastEndY2) / (x - lastEndX)
|
||||||
|
y2 = lastEndY2 + b
|
||||||
|
x = graphWidth.toDouble()
|
||||||
|
}
|
||||||
|
if (y1 < 0) { // end bottom
|
||||||
|
val b = (0 - lastEndY1) * (x - lastEndX) / (y1 - lastEndY1)
|
||||||
|
x = lastEndX + b
|
||||||
|
y1 = 0.0
|
||||||
|
}
|
||||||
|
if (y2 < 0) { // end bottom
|
||||||
|
val b = (0 - lastEndY2) * (x - lastEndX) / (y2 - lastEndY2)
|
||||||
|
x = lastEndX + b
|
||||||
|
y2 = 0.0
|
||||||
|
}
|
||||||
|
if (y1 > graphHeight) { // end top
|
||||||
|
val b = (graphHeight - lastEndY1) * (x - lastEndX) / (y1 - lastEndY1)
|
||||||
|
x = lastEndX + b
|
||||||
|
y1 = graphHeight.toDouble()
|
||||||
|
}
|
||||||
|
if (y2 > graphHeight) { // end top
|
||||||
|
val b = (graphHeight - lastEndY2) * (x - lastEndX) / (y2 - lastEndY2)
|
||||||
|
x = lastEndX + b
|
||||||
|
y2 = graphHeight.toDouble()
|
||||||
|
}
|
||||||
|
if (lastEndY1 < 0) { // start bottom
|
||||||
|
val b = (0 - y1) * (x - lastEndX) / (lastEndY1 - y1)
|
||||||
|
lastEndX = x - b
|
||||||
|
lastEndY1 = 0.0
|
||||||
|
}
|
||||||
|
if (lastEndY2 < 0) { // start bottom
|
||||||
|
val b = (0 - y2) * (x - lastEndX) / (lastEndY2 - y2)
|
||||||
|
lastEndX = x - b
|
||||||
|
lastEndY2 = 0.0
|
||||||
|
}
|
||||||
|
if (lastEndX < 0) { // start left
|
||||||
|
val b = (0 - x) * (y1 - lastEndY1) / (lastEndX - x)
|
||||||
|
lastEndY1 = y1 - b
|
||||||
|
lastEndX = 0.0
|
||||||
|
}
|
||||||
|
if (lastEndY1 > graphHeight) { // start top
|
||||||
|
val b = (graphHeight - y1) * (x - lastEndX) / (lastEndY1 - y1)
|
||||||
|
lastEndX = x - b
|
||||||
|
lastEndY1 = graphHeight.toDouble()
|
||||||
|
}
|
||||||
|
if (lastEndY2 > graphHeight) { // start top
|
||||||
|
val b = (graphHeight - y2) * (x - lastEndX) / (lastEndY2 - y2)
|
||||||
|
lastEndX = x - b
|
||||||
|
lastEndY2 = graphHeight.toDouble()
|
||||||
|
}
|
||||||
|
val startX = lastEndX.toFloat() + (graphLeft + 1)
|
||||||
|
val startY1 = (graphTop - lastEndY1).toFloat() + graphHeight
|
||||||
|
val startY2 = (graphTop - lastEndY2).toFloat() + graphHeight
|
||||||
|
val endX = x.toFloat() + (graphLeft + 1)
|
||||||
|
val endY1 = (graphTop - y1).toFloat() + graphHeight
|
||||||
|
val endY2 = (graphTop - y2).toFloat() + graphHeight
|
||||||
|
|
||||||
|
// draw data point
|
||||||
|
if (mStyles.drawDataPoints) {
|
||||||
|
//fix: last value was not drawn. Draw here now the end values
|
||||||
|
canvas.drawCircle(endX, endY1, mStyles.dataPointsRadius, mPaint)
|
||||||
|
canvas.drawCircle(endX, endY2, mStyles.dataPointsRadius, mPaint)
|
||||||
|
}
|
||||||
|
registerDataPoint(endX, endY1, value)
|
||||||
|
registerDataPoint(endX, endY2, value)
|
||||||
|
mPath.reset()
|
||||||
|
mSecondPath.reset()
|
||||||
|
mPath.moveTo(startX, startY1)
|
||||||
|
mSecondPath.moveTo(startX, startY2)
|
||||||
|
mPath.lineTo(endX, endY1)
|
||||||
|
mSecondPath.lineTo(endX, endY2)
|
||||||
|
canvas.drawPath(mPath, paint)
|
||||||
|
canvas.drawPath(mSecondPath, paint)
|
||||||
|
if (mStyles.drawBackground) {
|
||||||
|
canvas.drawRect(startX, startY2, endX, endY1, mPaintBackground)
|
||||||
|
}
|
||||||
|
} else if (mStyles.drawDataPoints) {
|
||||||
|
//fix: last value not drawn as datapoint. Draw first point here, and then on every step the end values (above)
|
||||||
|
//float first_X = (float) x + (graphLeft + 1);
|
||||||
|
//float first_Y = (float) (graphTop - y) + graphHeight;
|
||||||
|
// canvas.drawCircle(first_X, first_Y, dataPointsRadius, mPaint);
|
||||||
|
}
|
||||||
|
lastEndY1 = orgY1
|
||||||
|
lastEndY2 = orgY2
|
||||||
|
lastEndX = orgX
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (mStyles.drawBackground) {
|
||||||
|
// end / close path
|
||||||
|
mPathBackground.lineTo((float) lastUsedEndX, graphHeight + graphTop);
|
||||||
|
mPathBackground.lineTo(firstX, graphHeight + graphTop);
|
||||||
|
mPathBackground.close();
|
||||||
|
canvas.drawPath(mPathBackground, mPaintBackground);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
var thickness: Int
|
||||||
|
/**
|
||||||
|
* the thickness of the line.
|
||||||
|
* This option will be ignored if you are
|
||||||
|
* using a custom paint via [.setCustomPaint]
|
||||||
|
*
|
||||||
|
* @return the thickness of the line
|
||||||
|
*/
|
||||||
|
get() = mStyles.thickness
|
||||||
|
/**
|
||||||
|
* the thickness of the line.
|
||||||
|
* This option will be ignored if you are
|
||||||
|
* using a custom paint via [.setCustomPaint]
|
||||||
|
*
|
||||||
|
* @param thickness thickness of the line
|
||||||
|
*/
|
||||||
|
set(thickness) {
|
||||||
|
mStyles.thickness = thickness
|
||||||
|
}
|
||||||
|
var isDrawBackground: Boolean
|
||||||
|
/**
|
||||||
|
* flag whether the area under the line to the bottom
|
||||||
|
* of the viewport will be filled with a
|
||||||
|
* specific background color.
|
||||||
|
*
|
||||||
|
* @return whether the background will be drawn
|
||||||
|
* @see .getBackgroundColor
|
||||||
|
*/
|
||||||
|
get() = mStyles.drawBackground
|
||||||
|
/**
|
||||||
|
* flag whether the area under the line to the bottom
|
||||||
|
* of the viewport will be filled with a
|
||||||
|
* specific background color.
|
||||||
|
*
|
||||||
|
* @param drawBackground whether the background will be drawn
|
||||||
|
* @see .setBackgroundColor
|
||||||
|
*/
|
||||||
|
set(drawBackground) {
|
||||||
|
mStyles.drawBackground = drawBackground
|
||||||
|
}
|
||||||
|
var isDrawDataPoints: Boolean
|
||||||
|
/**
|
||||||
|
* flag whether the data points are highlighted as
|
||||||
|
* a visible point.
|
||||||
|
*
|
||||||
|
* @return flag whether the data points are highlighted
|
||||||
|
* @see .setDataPointsRadius
|
||||||
|
*/
|
||||||
|
get() = mStyles.drawDataPoints
|
||||||
|
/**
|
||||||
|
* flag whether the data points are highlighted as
|
||||||
|
* a visible point.
|
||||||
|
*
|
||||||
|
* @param drawDataPoints flag whether the data points are highlighted
|
||||||
|
* @see .setDataPointsRadius
|
||||||
|
*/
|
||||||
|
set(drawDataPoints) {
|
||||||
|
mStyles.drawDataPoints = drawDataPoints
|
||||||
|
}
|
||||||
|
var dataPointsRadius: Float
|
||||||
|
/**
|
||||||
|
* @return the radius for the data points.
|
||||||
|
* @see .setDrawDataPoints
|
||||||
|
*/
|
||||||
|
get() = mStyles.dataPointsRadius
|
||||||
|
/**
|
||||||
|
* @param dataPointsRadius the radius for the data points.
|
||||||
|
* @see .setDrawDataPoints
|
||||||
|
*/
|
||||||
|
set(dataPointsRadius) {
|
||||||
|
mStyles.dataPointsRadius = dataPointsRadius
|
||||||
|
}
|
||||||
|
var backgroundColor: Int
|
||||||
|
/**
|
||||||
|
* @return the background color for the filling under
|
||||||
|
* the line.
|
||||||
|
* @see .setDrawBackground
|
||||||
|
*/
|
||||||
|
get() = mStyles.backgroundColor
|
||||||
|
/**
|
||||||
|
* @param backgroundColor the background color for the filling under
|
||||||
|
* the line.
|
||||||
|
* @see .setDrawBackground
|
||||||
|
*/
|
||||||
|
set(backgroundColor) {
|
||||||
|
mStyles.backgroundColor = backgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* custom paint that can be used.
|
||||||
|
* this will ignore the thickness and color styles.
|
||||||
|
*
|
||||||
|
* @param customPaint the custom paint to be used for rendering the line
|
||||||
|
*/
|
||||||
|
fun setCustomPaint(customPaint: Paint?) {
|
||||||
|
mCustomPaint = customPaint
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,392 +0,0 @@
|
||||||
package info.nightscout.core.graph.data;
|
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Path;
|
|
||||||
|
|
||||||
import com.jjoe64.graphview.GraphView;
|
|
||||||
import com.jjoe64.graphview.series.BaseSeries;
|
|
||||||
import com.jjoe64.graphview.series.DataPointInterface;
|
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Series to plot the data as line.
|
|
||||||
* The line can be styled with many options.
|
|
||||||
*
|
|
||||||
* @author jjoe64
|
|
||||||
*/
|
|
||||||
public class FixedLineGraphSeries<E extends DataPointInterface> extends BaseSeries<E> {
|
|
||||||
/**
|
|
||||||
* wrapped styles regarding the line
|
|
||||||
*/
|
|
||||||
private static final class Styles {
|
|
||||||
/**
|
|
||||||
* the thickness of the line.
|
|
||||||
* This option will be ignored if you are
|
|
||||||
* using a custom paint via {@link #setCustomPaint(android.graphics.Paint)}
|
|
||||||
*/
|
|
||||||
private int thickness = 5;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* flag whether the area under the line to the bottom
|
|
||||||
* of the viewport will be filled with a
|
|
||||||
* specific background color.
|
|
||||||
*
|
|
||||||
* @see #backgroundColor
|
|
||||||
*/
|
|
||||||
private boolean drawBackground = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* flag whether the data points are highlighted as
|
|
||||||
* a visible point.
|
|
||||||
*
|
|
||||||
* @see #dataPointsRadius
|
|
||||||
*/
|
|
||||||
private boolean drawDataPoints = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the radius for the data points.
|
|
||||||
*
|
|
||||||
* @see #drawDataPoints
|
|
||||||
*/
|
|
||||||
private float dataPointsRadius = 10f;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the background color for the filling under
|
|
||||||
* the line.
|
|
||||||
*
|
|
||||||
* @see #drawBackground
|
|
||||||
*/
|
|
||||||
private int backgroundColor = Color.argb(100, 172, 218, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* wrapped styles
|
|
||||||
*/
|
|
||||||
private Styles mStyles;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* internal paint object
|
|
||||||
*/
|
|
||||||
private Paint mPaint;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* paint for the background
|
|
||||||
*/
|
|
||||||
private Paint mPaintBackground;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* path for the background filling
|
|
||||||
*/
|
|
||||||
private Path mPathBackground;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* path to the line
|
|
||||||
*/
|
|
||||||
private Path mPath;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* custom paint that can be used.
|
|
||||||
* this will ignore the thickness and color styles.
|
|
||||||
*/
|
|
||||||
private Paint mCustomPaint;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* creates a series without data
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused") public FixedLineGraphSeries() {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* creates a series with data
|
|
||||||
*
|
|
||||||
* @param data data points
|
|
||||||
*/
|
|
||||||
public FixedLineGraphSeries(E[] data) {
|
|
||||||
super(data);
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* do the initialization
|
|
||||||
* creates internal objects
|
|
||||||
*/
|
|
||||||
protected void init() {
|
|
||||||
mStyles = new Styles();
|
|
||||||
mPaint = new Paint();
|
|
||||||
mPaint.setStrokeCap(Paint.Cap.ROUND);
|
|
||||||
mPaint.setStyle(Paint.Style.STROKE);
|
|
||||||
mPaintBackground = new Paint();
|
|
||||||
|
|
||||||
mPathBackground = new Path();
|
|
||||||
mPath = new Path();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* plots the series
|
|
||||||
* draws the line and the background
|
|
||||||
*
|
|
||||||
* @param graphView graphview
|
|
||||||
* @param canvas canvas
|
|
||||||
* @param isSecondScale flag if it is the second scale
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void draw(GraphView graphView, Canvas canvas, boolean isSecondScale) {
|
|
||||||
resetDataPoints();
|
|
||||||
|
|
||||||
// get data
|
|
||||||
double maxX = graphView.getViewport().getMaxX(false);
|
|
||||||
double minX = graphView.getViewport().getMinX(false);
|
|
||||||
|
|
||||||
double maxY;
|
|
||||||
double minY;
|
|
||||||
if (isSecondScale) {
|
|
||||||
maxY = graphView.getSecondScale().getMaxY();
|
|
||||||
minY = graphView.getSecondScale().getMinY();
|
|
||||||
} else {
|
|
||||||
maxY = graphView.getViewport().getMaxY(false);
|
|
||||||
minY = graphView.getViewport().getMinY(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterator<E> values = getValues(minX, maxX);
|
|
||||||
|
|
||||||
// draw background
|
|
||||||
double lastEndY;
|
|
||||||
double lastEndX;
|
|
||||||
|
|
||||||
// draw data
|
|
||||||
mPaint.setStrokeWidth(mStyles.thickness);
|
|
||||||
mPaint.setColor(getColor());
|
|
||||||
mPaintBackground.setColor(mStyles.backgroundColor);
|
|
||||||
|
|
||||||
Paint paint;
|
|
||||||
if (mCustomPaint != null) {
|
|
||||||
paint = mCustomPaint;
|
|
||||||
} else {
|
|
||||||
paint = mPaint;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mStyles.drawBackground) {
|
|
||||||
mPathBackground.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
double diffY = maxY - minY;
|
|
||||||
double diffX = maxX - minX;
|
|
||||||
|
|
||||||
float graphHeight = graphView.getGraphContentHeight();
|
|
||||||
float graphWidth = graphView.getGraphContentWidth();
|
|
||||||
float graphLeft = graphView.getGraphContentLeft();
|
|
||||||
float graphTop = graphView.getGraphContentTop();
|
|
||||||
|
|
||||||
lastEndY = 0;
|
|
||||||
lastEndX = 0;
|
|
||||||
double lastUsedEndX = 0;
|
|
||||||
float firstX = 0;
|
|
||||||
int i=0;
|
|
||||||
while (values.hasNext()) {
|
|
||||||
E value = values.next();
|
|
||||||
|
|
||||||
double valY = value.getY() - minY;
|
|
||||||
double ratY = valY / diffY;
|
|
||||||
double y = graphHeight * ratY;
|
|
||||||
|
|
||||||
double valX = value.getX() - minX;
|
|
||||||
double ratX = valX / diffX;
|
|
||||||
double x = graphWidth * ratX;
|
|
||||||
|
|
||||||
double orgX = x;
|
|
||||||
double orgY = y;
|
|
||||||
|
|
||||||
if (i > 0) {
|
|
||||||
// overdraw
|
|
||||||
if (x > graphWidth) { // end right
|
|
||||||
double b = ((graphWidth - lastEndX) * (y - lastEndY)/(x - lastEndX));
|
|
||||||
y = lastEndY+b;
|
|
||||||
x = graphWidth;
|
|
||||||
}
|
|
||||||
if (y < 0) { // end bottom
|
|
||||||
double b = ((0 - lastEndY) * (x - lastEndX)/(y - lastEndY));
|
|
||||||
x = lastEndX+b;
|
|
||||||
y = 0;
|
|
||||||
}
|
|
||||||
if (y > graphHeight) { // end top
|
|
||||||
double b = ((graphHeight - lastEndY) * (x - lastEndX)/(y - lastEndY));
|
|
||||||
x = lastEndX+b;
|
|
||||||
y = graphHeight;
|
|
||||||
}
|
|
||||||
if (lastEndY < 0) { // start bottom
|
|
||||||
double b = ((0 - y) * (x - lastEndX)/(lastEndY - y));
|
|
||||||
lastEndX = x-b;
|
|
||||||
lastEndY = 0;
|
|
||||||
}
|
|
||||||
if (lastEndX < 0) { // start left
|
|
||||||
double b = ((0 - x) * (y - lastEndY)/(lastEndX - x));
|
|
||||||
lastEndY = y-b;
|
|
||||||
lastEndX = 0;
|
|
||||||
}
|
|
||||||
if (lastEndY > graphHeight) { // start top
|
|
||||||
double b = ((graphHeight - y) * (x - lastEndX)/(lastEndY - y));
|
|
||||||
lastEndX = x-b;
|
|
||||||
lastEndY = graphHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
float startX = (float) lastEndX + (graphLeft + 1);
|
|
||||||
float startY = (float) (graphTop - lastEndY) + graphHeight;
|
|
||||||
float endX = (float) x + (graphLeft + 1);
|
|
||||||
float endY = (float) (graphTop - y) + graphHeight;
|
|
||||||
|
|
||||||
// draw data point
|
|
||||||
if (mStyles.drawDataPoints) {
|
|
||||||
//fix: last value was not drawn. Draw here now the end values
|
|
||||||
canvas.drawCircle(endX, endY, mStyles.dataPointsRadius, mPaint);
|
|
||||||
}
|
|
||||||
registerDataPoint(endX, endY, value);
|
|
||||||
|
|
||||||
mPath.reset();
|
|
||||||
mPath.moveTo(startX, startY);
|
|
||||||
mPath.lineTo(endX, endY);
|
|
||||||
canvas.drawPath(mPath, paint);
|
|
||||||
if (mStyles.drawBackground) {
|
|
||||||
if (i==1) {
|
|
||||||
firstX = startX;
|
|
||||||
mPathBackground.moveTo(startX, startY);
|
|
||||||
}
|
|
||||||
mPathBackground.lineTo(endX, endY);
|
|
||||||
}
|
|
||||||
lastUsedEndX = endX;
|
|
||||||
} else if (mStyles.drawDataPoints) {
|
|
||||||
//fix: last value not drawn as datapoint. Draw first point here, and then on every step the end values (above)
|
|
||||||
// float first_X = (float) x + (graphLeft + 1);
|
|
||||||
// float first_Y = (float) (graphTop - y) + graphHeight;
|
|
||||||
// canvas.drawCircle(first_X, first_Y, dataPointsRadius, mPaint);
|
|
||||||
}
|
|
||||||
lastEndY = orgY;
|
|
||||||
lastEndX = orgX;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mStyles.drawBackground) {
|
|
||||||
// end / close path
|
|
||||||
mPathBackground.lineTo((float) lastUsedEndX, (float) (graphTop - (-minY / diffY * graphHeight)) + graphHeight);
|
|
||||||
mPathBackground.lineTo(firstX, (float) (graphTop - (-minY / diffY * graphHeight)) + graphHeight);
|
|
||||||
mPathBackground.close();
|
|
||||||
canvas.drawPath(mPathBackground, mPaintBackground);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the thickness of the line.
|
|
||||||
* This option will be ignored if you are
|
|
||||||
* using a custom paint via {@link #setCustomPaint(android.graphics.Paint)}
|
|
||||||
*
|
|
||||||
* @return the thickness of the line
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused") public int getThickness() {
|
|
||||||
return mStyles.thickness;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the thickness of the line.
|
|
||||||
* This option will be ignored if you are
|
|
||||||
* using a custom paint via {@link #setCustomPaint(android.graphics.Paint)}
|
|
||||||
*
|
|
||||||
* @param thickness thickness of the line
|
|
||||||
*/
|
|
||||||
public void setThickness(int thickness) {
|
|
||||||
mStyles.thickness = thickness;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* flag whether the area under the line to the bottom
|
|
||||||
* of the viewport will be filled with a
|
|
||||||
* specific background color.
|
|
||||||
*
|
|
||||||
* @return whether the background will be drawn
|
|
||||||
* @see #getBackgroundColor()
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused") public boolean isDrawBackground() {
|
|
||||||
return mStyles.drawBackground;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* flag whether the area under the line to the bottom
|
|
||||||
* of the viewport will be filled with a
|
|
||||||
* specific background color.
|
|
||||||
*
|
|
||||||
* @param drawBackground whether the background will be drawn
|
|
||||||
* @see #setBackgroundColor(int)
|
|
||||||
*/
|
|
||||||
public void setDrawBackground(boolean drawBackground) {
|
|
||||||
mStyles.drawBackground = drawBackground;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* flag whether the data points are highlighted as
|
|
||||||
* a visible point.
|
|
||||||
*
|
|
||||||
* @return flag whether the data points are highlighted
|
|
||||||
* @see #setDataPointsRadius(float)
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused") public boolean isDrawDataPoints() {
|
|
||||||
return mStyles.drawDataPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* flag whether the data points are highlighted as
|
|
||||||
* a visible point.
|
|
||||||
*
|
|
||||||
* @param drawDataPoints flag whether the data points are highlighted
|
|
||||||
* @see #setDataPointsRadius(float)
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused") public void setDrawDataPoints(boolean drawDataPoints) {
|
|
||||||
mStyles.drawDataPoints = drawDataPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the radius for the data points.
|
|
||||||
* @see #setDrawDataPoints(boolean)
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused") public float getDataPointsRadius() {
|
|
||||||
return mStyles.dataPointsRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param dataPointsRadius the radius for the data points.
|
|
||||||
* @see #setDrawDataPoints(boolean)
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused") public void setDataPointsRadius(float dataPointsRadius) {
|
|
||||||
mStyles.dataPointsRadius = dataPointsRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the background color for the filling under
|
|
||||||
* the line.
|
|
||||||
* @see #setDrawBackground(boolean)
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused") public int getBackgroundColor() {
|
|
||||||
return mStyles.backgroundColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param backgroundColor the background color for the filling under
|
|
||||||
* the line.
|
|
||||||
* @see #setDrawBackground(boolean)
|
|
||||||
*/
|
|
||||||
public void setBackgroundColor(int backgroundColor) {
|
|
||||||
mStyles.backgroundColor = backgroundColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* custom paint that can be used.
|
|
||||||
* this will ignore the thickness and color styles.
|
|
||||||
*
|
|
||||||
* @param customPaint the custom paint to be used for rendering the line
|
|
||||||
*/
|
|
||||||
public void setCustomPaint(Paint customPaint) {
|
|
||||||
this.mCustomPaint = customPaint;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,356 @@
|
||||||
|
package info.nightscout.core.graph.data
|
||||||
|
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.Path
|
||||||
|
import com.jjoe64.graphview.GraphView
|
||||||
|
import com.jjoe64.graphview.series.BaseSeries
|
||||||
|
import com.jjoe64.graphview.series.DataPointInterface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Series to plot the data as line.
|
||||||
|
* The line can be styled with many options.
|
||||||
|
*
|
||||||
|
* @author jjoe64
|
||||||
|
*/
|
||||||
|
@Suppress("unused") class FixedLineGraphSeries<E : DataPointInterface?> : BaseSeries<E> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wrapped styles regarding the line
|
||||||
|
*/
|
||||||
|
private class Styles {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the thickness of the line.
|
||||||
|
* This option will be ignored if you are
|
||||||
|
* using a custom paint via [.setCustomPaint]
|
||||||
|
*/
|
||||||
|
var thickness = 5
|
||||||
|
|
||||||
|
/**
|
||||||
|
* flag whether the area under the line to the bottom
|
||||||
|
* of the viewport will be filled with a
|
||||||
|
* specific background color.
|
||||||
|
*
|
||||||
|
* @see .backgroundColor
|
||||||
|
*/
|
||||||
|
var drawBackground = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* flag whether the data points are highlighted as
|
||||||
|
* a visible point.
|
||||||
|
*
|
||||||
|
* @see .dataPointsRadius
|
||||||
|
*/
|
||||||
|
var drawDataPoints = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the radius for the data points.
|
||||||
|
*
|
||||||
|
* @see .drawDataPoints
|
||||||
|
*/
|
||||||
|
var dataPointsRadius = 10f
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the background color for the filling under
|
||||||
|
* the line.
|
||||||
|
*
|
||||||
|
* @see .drawBackground
|
||||||
|
*/
|
||||||
|
var backgroundColor = Color.argb(100, 172, 218, 255)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wrapped styles
|
||||||
|
*/
|
||||||
|
private lateinit var mStyles: Styles
|
||||||
|
|
||||||
|
/**
|
||||||
|
* internal paint object
|
||||||
|
*/
|
||||||
|
private lateinit var mPaint: Paint
|
||||||
|
|
||||||
|
/**
|
||||||
|
* paint for the background
|
||||||
|
*/
|
||||||
|
private lateinit var mPaintBackground: Paint
|
||||||
|
|
||||||
|
/**
|
||||||
|
* path for the background filling
|
||||||
|
*/
|
||||||
|
private lateinit var mPathBackground: Path
|
||||||
|
|
||||||
|
/**
|
||||||
|
* path to the line
|
||||||
|
*/
|
||||||
|
private lateinit var mPath: Path
|
||||||
|
|
||||||
|
/**
|
||||||
|
* custom paint that can be used.
|
||||||
|
* this will ignore the thickness and color styles.
|
||||||
|
*/
|
||||||
|
private var mCustomPaint: Paint? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates a series without data
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
constructor() {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates a series with data
|
||||||
|
*
|
||||||
|
* @param data data points
|
||||||
|
*/
|
||||||
|
constructor(data: Array<E>?) : super(data) {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* do the initialization
|
||||||
|
* creates internal objects
|
||||||
|
*/
|
||||||
|
private fun init() {
|
||||||
|
mStyles = Styles()
|
||||||
|
mPaint = Paint()
|
||||||
|
mPaint.strokeCap = Paint.Cap.ROUND
|
||||||
|
mPaint.style = Paint.Style.STROKE
|
||||||
|
mPaintBackground = Paint()
|
||||||
|
mPathBackground = Path()
|
||||||
|
mPath = Path()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* plots the series
|
||||||
|
* draws the line and the background
|
||||||
|
*
|
||||||
|
* @param graphView graphview
|
||||||
|
* @param canvas canvas
|
||||||
|
* @param isSecondScale flag if it is the second scale
|
||||||
|
*/
|
||||||
|
override fun draw(graphView: GraphView, canvas: Canvas, isSecondScale: Boolean) {
|
||||||
|
resetDataPoints()
|
||||||
|
|
||||||
|
// get data
|
||||||
|
val maxX = graphView.viewport.getMaxX(false)
|
||||||
|
val minX = graphView.viewport.getMinX(false)
|
||||||
|
val maxY: Double
|
||||||
|
val minY: Double
|
||||||
|
if (isSecondScale) {
|
||||||
|
maxY = graphView.secondScale.maxY
|
||||||
|
minY = graphView.secondScale.minY
|
||||||
|
} else {
|
||||||
|
maxY = graphView.viewport.getMaxY(false)
|
||||||
|
minY = graphView.viewport.getMinY(false)
|
||||||
|
}
|
||||||
|
val values = getValues(minX, maxX)
|
||||||
|
|
||||||
|
// draw background
|
||||||
|
var lastEndY: Double
|
||||||
|
var lastEndX: Double
|
||||||
|
|
||||||
|
// draw data
|
||||||
|
mPaint.strokeWidth = mStyles.thickness.toFloat()
|
||||||
|
mPaint.color = color
|
||||||
|
mPaintBackground.color = mStyles.backgroundColor
|
||||||
|
val paint = mCustomPaint ?: mPaint
|
||||||
|
if (mStyles.drawBackground) {
|
||||||
|
mPathBackground.reset()
|
||||||
|
}
|
||||||
|
val diffY = maxY - minY
|
||||||
|
val diffX = maxX - minX
|
||||||
|
val graphHeight = graphView.graphContentHeight.toFloat()
|
||||||
|
val graphWidth = graphView.graphContentWidth.toFloat()
|
||||||
|
val graphLeft = graphView.graphContentLeft.toFloat()
|
||||||
|
val graphTop = graphView.graphContentTop.toFloat()
|
||||||
|
lastEndY = 0.0
|
||||||
|
lastEndX = 0.0
|
||||||
|
var lastUsedEndX = 0.0
|
||||||
|
var firstX = 0f
|
||||||
|
var i = 0
|
||||||
|
while (values.hasNext()) {
|
||||||
|
val value = values.next() ?: break
|
||||||
|
val valY = value.y - minY
|
||||||
|
val ratY = valY / diffY
|
||||||
|
var y = graphHeight * ratY
|
||||||
|
val valX = value.x - minX
|
||||||
|
val ratX = valX / diffX
|
||||||
|
var x = graphWidth * ratX
|
||||||
|
val orgX = x
|
||||||
|
val orgY = y
|
||||||
|
@Suppress("ControlFlowWithEmptyBody")
|
||||||
|
if (i > 0) {
|
||||||
|
// overdraw
|
||||||
|
if (x > graphWidth) { // end right
|
||||||
|
val b = (graphWidth - lastEndX) * (y - lastEndY) / (x - lastEndX)
|
||||||
|
y = lastEndY + b
|
||||||
|
x = graphWidth.toDouble()
|
||||||
|
}
|
||||||
|
if (y < 0) { // end bottom
|
||||||
|
val b = (0 - lastEndY) * (x - lastEndX) / (y - lastEndY)
|
||||||
|
x = lastEndX + b
|
||||||
|
y = 0.0
|
||||||
|
}
|
||||||
|
if (y > graphHeight) { // end top
|
||||||
|
val b = (graphHeight - lastEndY) * (x - lastEndX) / (y - lastEndY)
|
||||||
|
x = lastEndX + b
|
||||||
|
y = graphHeight.toDouble()
|
||||||
|
}
|
||||||
|
if (lastEndY < 0) { // start bottom
|
||||||
|
val b = (0 - y) * (x - lastEndX) / (lastEndY - y)
|
||||||
|
lastEndX = x - b
|
||||||
|
lastEndY = 0.0
|
||||||
|
}
|
||||||
|
if (lastEndX < 0) { // start left
|
||||||
|
val b = (0 - x) * (y - lastEndY) / (lastEndX - x)
|
||||||
|
lastEndY = y - b
|
||||||
|
lastEndX = 0.0
|
||||||
|
}
|
||||||
|
if (lastEndY > graphHeight) { // start top
|
||||||
|
val b = (graphHeight - y) * (x - lastEndX) / (lastEndY - y)
|
||||||
|
lastEndX = x - b
|
||||||
|
lastEndY = graphHeight.toDouble()
|
||||||
|
}
|
||||||
|
val startX = lastEndX.toFloat() + (graphLeft + 1)
|
||||||
|
val startY = (graphTop - lastEndY).toFloat() + graphHeight
|
||||||
|
val endX = x.toFloat() + (graphLeft + 1)
|
||||||
|
val endY = (graphTop - y).toFloat() + graphHeight
|
||||||
|
|
||||||
|
// draw data point
|
||||||
|
if (mStyles.drawDataPoints) {
|
||||||
|
//fix: last value was not drawn. Draw here now the end values
|
||||||
|
canvas.drawCircle(endX, endY, mStyles.dataPointsRadius, mPaint)
|
||||||
|
}
|
||||||
|
registerDataPoint(endX, endY, value)
|
||||||
|
mPath.reset()
|
||||||
|
mPath.moveTo(startX, startY)
|
||||||
|
mPath.lineTo(endX, endY)
|
||||||
|
canvas.drawPath(mPath, paint)
|
||||||
|
if (mStyles.drawBackground) {
|
||||||
|
if (i == 1) {
|
||||||
|
firstX = startX
|
||||||
|
mPathBackground.moveTo(startX, startY)
|
||||||
|
}
|
||||||
|
mPathBackground.lineTo(endX, endY)
|
||||||
|
}
|
||||||
|
lastUsedEndX = endX.toDouble()
|
||||||
|
} else if (mStyles.drawDataPoints) {
|
||||||
|
//fix: last value not drawn as datapoint. Draw first point here, and then on every step the end values (above)
|
||||||
|
// float first_X = (float) x + (graphLeft + 1);
|
||||||
|
// float first_Y = (float) (graphTop - y) + graphHeight;
|
||||||
|
// canvas.drawCircle(first_X, first_Y, dataPointsRadius, mPaint);
|
||||||
|
}
|
||||||
|
lastEndY = orgY
|
||||||
|
lastEndX = orgX
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if (mStyles.drawBackground) {
|
||||||
|
// end / close path
|
||||||
|
mPathBackground.lineTo(lastUsedEndX.toFloat(), (graphTop - -minY / diffY * graphHeight).toFloat() + graphHeight)
|
||||||
|
mPathBackground.lineTo(firstX, (graphTop - -minY / diffY * graphHeight).toFloat() + graphHeight)
|
||||||
|
mPathBackground.close()
|
||||||
|
canvas.drawPath(mPathBackground, mPaintBackground)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var thickness: Int
|
||||||
|
/**
|
||||||
|
* the thickness of the line.
|
||||||
|
* This option will be ignored if you are
|
||||||
|
* using a custom paint via [.setCustomPaint]
|
||||||
|
*
|
||||||
|
* @return the thickness of the line
|
||||||
|
*/
|
||||||
|
get() = mStyles.thickness
|
||||||
|
/**
|
||||||
|
* the thickness of the line.
|
||||||
|
* This option will be ignored if you are
|
||||||
|
* using a custom paint via [.setCustomPaint]
|
||||||
|
*
|
||||||
|
* @param thickness thickness of the line
|
||||||
|
*/
|
||||||
|
set(thickness) {
|
||||||
|
mStyles.thickness = thickness
|
||||||
|
}
|
||||||
|
var isDrawBackground: Boolean
|
||||||
|
/**
|
||||||
|
* flag whether the area under the line to the bottom
|
||||||
|
* of the viewport will be filled with a
|
||||||
|
* specific background color.
|
||||||
|
*
|
||||||
|
* @return whether the background will be drawn
|
||||||
|
* @see .getBackgroundColor
|
||||||
|
*/
|
||||||
|
get() = mStyles.drawBackground
|
||||||
|
/**
|
||||||
|
* flag whether the area under the line to the bottom
|
||||||
|
* of the viewport will be filled with a
|
||||||
|
* specific background color.
|
||||||
|
*
|
||||||
|
* @param drawBackground whether the background will be drawn
|
||||||
|
* @see .setBackgroundColor
|
||||||
|
*/
|
||||||
|
set(drawBackground) {
|
||||||
|
mStyles.drawBackground = drawBackground
|
||||||
|
}
|
||||||
|
var isDrawDataPoints: Boolean
|
||||||
|
/**
|
||||||
|
* flag whether the data points are highlighted as
|
||||||
|
* a visible point.
|
||||||
|
*
|
||||||
|
* @return flag whether the data points are highlighted
|
||||||
|
* @see .setDataPointsRadius
|
||||||
|
*/
|
||||||
|
get() = mStyles.drawDataPoints
|
||||||
|
/**
|
||||||
|
* flag whether the data points are highlighted as
|
||||||
|
* a visible point.
|
||||||
|
*
|
||||||
|
* @param drawDataPoints flag whether the data points are highlighted
|
||||||
|
* @see .setDataPointsRadius
|
||||||
|
*/
|
||||||
|
set(drawDataPoints) {
|
||||||
|
mStyles.drawDataPoints = drawDataPoints
|
||||||
|
}
|
||||||
|
var dataPointsRadius: Float
|
||||||
|
/**
|
||||||
|
* @return the radius for the data points.
|
||||||
|
* @see .setDrawDataPoints
|
||||||
|
*/
|
||||||
|
get() = mStyles.dataPointsRadius
|
||||||
|
/**
|
||||||
|
* @param dataPointsRadius the radius for the data points.
|
||||||
|
* @see .setDrawDataPoints
|
||||||
|
*/
|
||||||
|
set(dataPointsRadius) {
|
||||||
|
mStyles.dataPointsRadius = dataPointsRadius
|
||||||
|
}
|
||||||
|
var backgroundColor: Int
|
||||||
|
/**
|
||||||
|
* @return the background color for the filling under
|
||||||
|
* the line.
|
||||||
|
* @see .setDrawBackground
|
||||||
|
*/
|
||||||
|
get() = mStyles.backgroundColor
|
||||||
|
/**
|
||||||
|
* @param backgroundColor the background color for the filling under
|
||||||
|
* the line.
|
||||||
|
* @see .setDrawBackground
|
||||||
|
*/
|
||||||
|
set(backgroundColor) {
|
||||||
|
mStyles.backgroundColor = backgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* custom paint that can be used.
|
||||||
|
* this will ignore the thickness and color styles.
|
||||||
|
*
|
||||||
|
* @param customPaint the custom paint to be used for rendering the line
|
||||||
|
*/
|
||||||
|
fun setCustomPaint(customPaint: Paint?) {
|
||||||
|
mCustomPaint = customPaint
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,382 +0,0 @@
|
||||||
package info.nightscout.core.graph.data;
|
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Path;
|
|
||||||
import android.graphics.Point;
|
|
||||||
import android.graphics.PorterDuff;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.graphics.Typeface;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
|
|
||||||
import com.jjoe64.graphview.GraphView;
|
|
||||||
import com.jjoe64.graphview.series.BaseSeries;
|
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
import info.nightscout.core.main.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Series that plots the data as points.
|
|
||||||
* The points can be different shapes or a
|
|
||||||
* complete custom drawing.
|
|
||||||
*
|
|
||||||
* @author jjoe64
|
|
||||||
*/
|
|
||||||
public class PointsWithLabelGraphSeries<E extends DataPointWithLabelInterface> extends BaseSeries<E> {
|
|
||||||
// Default spSize
|
|
||||||
int spSize = 14;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* choose a predefined shape to render for
|
|
||||||
* each data point.
|
|
||||||
* You can also render a custom drawing via {@link com.jjoe64.graphview.series.PointsGraphSeries.CustomShape}
|
|
||||||
*/
|
|
||||||
public enum Shape {
|
|
||||||
BG,
|
|
||||||
PREDICTION,
|
|
||||||
TRIANGLE,
|
|
||||||
RECTANGLE,
|
|
||||||
BOLUS,
|
|
||||||
CARBS,
|
|
||||||
SMB,
|
|
||||||
EXTENDEDBOLUS,
|
|
||||||
PROFILE,
|
|
||||||
MBG,
|
|
||||||
BGCHECK,
|
|
||||||
ANNOUNCEMENT,
|
|
||||||
OPENAPS_OFFLINE,
|
|
||||||
EXERCISE,
|
|
||||||
GENERAL,
|
|
||||||
GENERAL_WITH_DURATION,
|
|
||||||
COB_FAIL_OVER,
|
|
||||||
IOB_PREDICTION,
|
|
||||||
BUCKETED_BG,
|
|
||||||
HEARTRATE,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* internal paint object
|
|
||||||
*/
|
|
||||||
private Paint mPaint;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* creates the series without data
|
|
||||||
*/
|
|
||||||
public PointsWithLabelGraphSeries() {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* creates the series with data
|
|
||||||
*
|
|
||||||
* @param data dataPoints
|
|
||||||
*/
|
|
||||||
public PointsWithLabelGraphSeries(E[] data) {
|
|
||||||
super(data);
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* init the internal objects
|
|
||||||
* set the defaults
|
|
||||||
*/
|
|
||||||
protected void init() {
|
|
||||||
mPaint = new Paint();
|
|
||||||
mPaint.setStrokeCap(Paint.Cap.ROUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* plot the data to the viewport
|
|
||||||
*
|
|
||||||
* @param graphView graphview
|
|
||||||
* @param canvas canvas to draw on
|
|
||||||
* @param isSecondScale whether it is the second scale
|
|
||||||
*/
|
|
||||||
@SuppressWarnings({"deprecation"})
|
|
||||||
@Override
|
|
||||||
public void draw(GraphView graphView, Canvas canvas, boolean isSecondScale) {
|
|
||||||
// Convert the sp to pixels
|
|
||||||
float scaledTextSize = spSize * graphView.getContext().getResources().getDisplayMetrics().scaledDensity;
|
|
||||||
float scaledPxSize = graphView.getContext().getResources().getDisplayMetrics().scaledDensity * 3f;
|
|
||||||
resetDataPoints();
|
|
||||||
|
|
||||||
// get data
|
|
||||||
double maxX = graphView.getViewport().getMaxX(false);
|
|
||||||
double minX = graphView.getViewport().getMinX(false);
|
|
||||||
|
|
||||||
double maxY;
|
|
||||||
double minY;
|
|
||||||
if (isSecondScale) {
|
|
||||||
maxY = graphView.getSecondScale().getMaxY();
|
|
||||||
minY = graphView.getSecondScale().getMinY();
|
|
||||||
} else {
|
|
||||||
maxY = graphView.getViewport().getMaxY(false);
|
|
||||||
minY = graphView.getViewport().getMinY(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterator<E> values = getValues(minX, maxX);
|
|
||||||
|
|
||||||
// draw background
|
|
||||||
// draw data
|
|
||||||
|
|
||||||
double diffY = maxY - minY;
|
|
||||||
double diffX = maxX - minX;
|
|
||||||
|
|
||||||
float graphHeight = graphView.getGraphContentHeight();
|
|
||||||
float graphWidth = graphView.getGraphContentWidth();
|
|
||||||
float graphLeft = graphView.getGraphContentLeft();
|
|
||||||
float graphTop = graphView.getGraphContentTop();
|
|
||||||
|
|
||||||
float scaleX = (float) (graphWidth / diffX);
|
|
||||||
|
|
||||||
while (values.hasNext()) {
|
|
||||||
E value = values.next();
|
|
||||||
|
|
||||||
mPaint.setColor(value.color(graphView.getContext()));
|
|
||||||
|
|
||||||
double valY = value.getY() - minY;
|
|
||||||
double ratY = valY / diffY;
|
|
||||||
double y = graphHeight * ratY;
|
|
||||||
|
|
||||||
double valX = value.getX() - minX;
|
|
||||||
double ratX = valX / diffX;
|
|
||||||
double x = graphWidth * ratX;
|
|
||||||
|
|
||||||
// overdraw
|
|
||||||
boolean overdraw = x > graphWidth;
|
|
||||||
// end right
|
|
||||||
if (y < 0) { // end bottom
|
|
||||||
overdraw = true;
|
|
||||||
}
|
|
||||||
if (y > graphHeight) { // end top
|
|
||||||
overdraw = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
long duration = value.getDuration();
|
|
||||||
float endWithDuration = (float) (x + duration * scaleX + graphLeft + 1);
|
|
||||||
// cut off to graph start if needed
|
|
||||||
if (x < 0 && endWithDuration > 0) {
|
|
||||||
x = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fix a bug that continue to show the DOT after Y axis */
|
|
||||||
if (x < 0) {
|
|
||||||
overdraw = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
float endX = (float) x + (graphLeft + 1);
|
|
||||||
float endY = (float) (graphTop - y) + graphHeight;
|
|
||||||
registerDataPoint(endX, endY, value);
|
|
||||||
|
|
||||||
float xPlusLength = 0;
|
|
||||||
if (duration > 0) {
|
|
||||||
xPlusLength = Math.min(endWithDuration, graphLeft + graphWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw data point
|
|
||||||
if (!overdraw) {
|
|
||||||
if (value.getShape() == Shape.BG || value.getShape() == Shape.COB_FAIL_OVER) {
|
|
||||||
mPaint.setStyle(value.getPaintStyle());
|
|
||||||
mPaint.setStrokeWidth(0);
|
|
||||||
canvas.drawCircle(endX, endY, value.getSize() * scaledPxSize, mPaint);
|
|
||||||
} else if (value.getShape() == Shape.BG || value.getShape() == Shape.IOB_PREDICTION || value.getShape() == Shape.BUCKETED_BG) {
|
|
||||||
mPaint.setColor(value.color(graphView.getContext()));
|
|
||||||
mPaint.setStyle(value.getPaintStyle());
|
|
||||||
mPaint.setStrokeWidth(0);
|
|
||||||
canvas.drawCircle(endX, endY, value.getSize() * scaledPxSize, mPaint);
|
|
||||||
} else if (value.getShape() == Shape.PREDICTION) {
|
|
||||||
mPaint.setColor(value.color(graphView.getContext()));
|
|
||||||
mPaint.setStyle(value.getPaintStyle());
|
|
||||||
mPaint.setStrokeWidth(0);
|
|
||||||
canvas.drawCircle(endX, endY, scaledPxSize, mPaint);
|
|
||||||
mPaint.setStyle(value.getPaintStyle());
|
|
||||||
mPaint.setStrokeWidth(0);
|
|
||||||
canvas.drawCircle(endX, endY, scaledPxSize / 3, mPaint);
|
|
||||||
} else if (value.getShape() == Shape.RECTANGLE) {
|
|
||||||
canvas.drawRect(endX - scaledPxSize, endY - scaledPxSize, endX + scaledPxSize, endY + scaledPxSize, mPaint);
|
|
||||||
} else if (value.getShape() == Shape.TRIANGLE) {
|
|
||||||
mPaint.setStrokeWidth(0);
|
|
||||||
Point[] points = new Point[3];
|
|
||||||
points[0] = new Point((int) endX, (int) (endY - scaledPxSize));
|
|
||||||
points[1] = new Point((int) (endX + scaledPxSize), (int) (endY + scaledPxSize * 0.67));
|
|
||||||
points[2] = new Point((int) (endX - scaledPxSize), (int) (endY + scaledPxSize * 0.67));
|
|
||||||
drawArrows(points, canvas, mPaint);
|
|
||||||
} else if (value.getShape() == Shape.BOLUS) {
|
|
||||||
mPaint.setStrokeWidth(0);
|
|
||||||
Point[] points = new Point[3];
|
|
||||||
points[0] = new Point((int) endX, (int) (endY - scaledPxSize));
|
|
||||||
points[1] = new Point((int) (endX + scaledPxSize), (int) (endY + scaledPxSize * 0.67));
|
|
||||||
points[2] = new Point((int) (endX - scaledPxSize), (int) (endY + scaledPxSize * 0.67));
|
|
||||||
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
|
||||||
drawArrows(points, canvas, mPaint);
|
|
||||||
if (!value.getLabel().isEmpty())
|
|
||||||
drawLabel45Right(endX, endY, value, canvas, scaledPxSize, scaledTextSize);
|
|
||||||
} else if (value.getShape() == Shape.CARBS) {
|
|
||||||
mPaint.setStrokeWidth(0);
|
|
||||||
Point[] points = new Point[3];
|
|
||||||
points[0] = new Point((int) endX, (int) (endY - scaledPxSize));
|
|
||||||
points[1] = new Point((int) (endX + scaledPxSize), (int) (endY + scaledPxSize * 0.67));
|
|
||||||
points[2] = new Point((int) (endX - scaledPxSize), (int) (endY + scaledPxSize * 0.67));
|
|
||||||
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
|
||||||
drawArrows(points, canvas, mPaint);
|
|
||||||
if (!value.getLabel().isEmpty())
|
|
||||||
drawLabel45Left(endX, endY, value, canvas, scaledPxSize, scaledTextSize);
|
|
||||||
} else if (value.getShape() == Shape.SMB) {
|
|
||||||
mPaint.setStrokeWidth(2);
|
|
||||||
Point[] points = new Point[3];
|
|
||||||
float size = value.getSize() * scaledPxSize;
|
|
||||||
points[0] = new Point((int) endX, (int) (endY - size));
|
|
||||||
points[1] = new Point((int) (endX + size), (int) (endY + size * 0.67));
|
|
||||||
points[2] = new Point((int) (endX - size), (int) (endY + size * 0.67));
|
|
||||||
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
|
||||||
drawArrows(points, canvas, mPaint);
|
|
||||||
} else if (value.getShape() == Shape.EXTENDEDBOLUS) {
|
|
||||||
mPaint.setStrokeWidth(0);
|
|
||||||
if (!value.getLabel().isEmpty()) {
|
|
||||||
Rect bounds = new Rect((int) endX, (int) endY + 3, (int) (xPlusLength), (int) endY + 8);
|
|
||||||
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
|
||||||
canvas.drawRect(bounds, mPaint);
|
|
||||||
mPaint.setTextSize(scaledTextSize);
|
|
||||||
mPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL));
|
|
||||||
mPaint.setFakeBoldText(true);
|
|
||||||
canvas.drawText(value.getLabel(), endX, endY, mPaint);
|
|
||||||
}
|
|
||||||
} else if (value.getShape() == Shape.HEARTRATE) {
|
|
||||||
mPaint.setStrokeWidth(0);
|
|
||||||
Rect bounds = new Rect((int) endX, (int) endY - 8, (int) (xPlusLength), (int) endY + 8);
|
|
||||||
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
|
||||||
canvas.drawRect(bounds, mPaint);
|
|
||||||
} else if (value.getShape() == Shape.PROFILE) {
|
|
||||||
Drawable drawable = ContextCompat.getDrawable(graphView.getContext(), R.drawable.ic_ribbon_profile);
|
|
||||||
assert drawable != null;
|
|
||||||
drawable.setColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY);
|
|
||||||
drawable.setBounds(
|
|
||||||
(int) (endX - drawable.getIntrinsicWidth() / 2),
|
|
||||||
(int) (endY - drawable.getIntrinsicHeight() / 2),
|
|
||||||
(int) (endX + drawable.getIntrinsicWidth() / 2),
|
|
||||||
(int) (endY + drawable.getIntrinsicHeight() / 2));
|
|
||||||
drawable.draw(canvas);
|
|
||||||
|
|
||||||
mPaint.setTextSize(scaledTextSize * 0.8f);
|
|
||||||
mPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL));
|
|
||||||
mPaint.setColor(value.color(graphView.getContext()));
|
|
||||||
Rect bounds = new Rect();
|
|
||||||
mPaint.getTextBounds(value.getLabel(), 0, value.getLabel().length(), bounds);
|
|
||||||
float px = endX - bounds.width() / 2.0f;
|
|
||||||
float py = endY + drawable.getIntrinsicHeight();
|
|
||||||
mPaint.setStyle(Paint.Style.FILL);
|
|
||||||
canvas.drawText(value.getLabel(), px, py, mPaint);
|
|
||||||
} else if (value.getShape() == Shape.MBG) {
|
|
||||||
mPaint.setStyle(Paint.Style.STROKE);
|
|
||||||
mPaint.setStrokeWidth(5);
|
|
||||||
canvas.drawCircle(endX, endY, scaledPxSize, mPaint);
|
|
||||||
} else if (value.getShape() == Shape.BGCHECK) {
|
|
||||||
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
|
||||||
mPaint.setStrokeWidth(0);
|
|
||||||
canvas.drawCircle(endX, endY, scaledPxSize, mPaint);
|
|
||||||
if (!value.getLabel().isEmpty()) {
|
|
||||||
drawLabel45Right(endX, endY, value, canvas, scaledPxSize, scaledTextSize);
|
|
||||||
}
|
|
||||||
} else if (value.getShape() == Shape.ANNOUNCEMENT) {
|
|
||||||
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
|
||||||
mPaint.setStrokeWidth(0);
|
|
||||||
canvas.drawCircle(endX, endY, scaledPxSize, mPaint);
|
|
||||||
if (!value.getLabel().isEmpty()) {
|
|
||||||
drawLabel45Right(endX, endY, value, canvas, scaledPxSize, scaledTextSize);
|
|
||||||
}
|
|
||||||
} else if (value.getShape() == Shape.GENERAL) {
|
|
||||||
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
|
||||||
mPaint.setStrokeWidth(0);
|
|
||||||
canvas.drawCircle(endX, endY, scaledPxSize, mPaint);
|
|
||||||
if (!value.getLabel().isEmpty()) {
|
|
||||||
drawLabel45Right(endX, endY, value, canvas, scaledPxSize, scaledTextSize);
|
|
||||||
}
|
|
||||||
} else if (value.getShape() == Shape.EXERCISE) {
|
|
||||||
mPaint.setStrokeWidth(0);
|
|
||||||
if (!value.getLabel().isEmpty()) {
|
|
||||||
mPaint.setStrokeWidth(0);
|
|
||||||
mPaint.setTextSize((float) (scaledTextSize * 1.2));
|
|
||||||
mPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
|
|
||||||
Rect bounds = new Rect();
|
|
||||||
mPaint.getTextBounds(value.getLabel(), 0, value.getLabel().length(), bounds);
|
|
||||||
mPaint.setStyle(Paint.Style.STROKE);
|
|
||||||
float py = graphTop + 20;
|
|
||||||
canvas.drawText(value.getLabel(), endX, py, mPaint);
|
|
||||||
mPaint.setStrokeWidth(5);
|
|
||||||
canvas.drawRect(endX - 3, bounds.top + py - 3, xPlusLength + 3, bounds.bottom + py + 3, mPaint);
|
|
||||||
}
|
|
||||||
} else if (value.getShape() == Shape.OPENAPS_OFFLINE && value.getDuration() != 0) {
|
|
||||||
mPaint.setStrokeWidth(0);
|
|
||||||
if (!value.getLabel().isEmpty()) {
|
|
||||||
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
|
||||||
mPaint.setStrokeWidth(5);
|
|
||||||
canvas.drawRect(endX - 3, graphTop, xPlusLength + 3, graphTop + graphHeight, mPaint);
|
|
||||||
}
|
|
||||||
} else if (value.getShape() == Shape.GENERAL_WITH_DURATION) {
|
|
||||||
mPaint.setStrokeWidth(0);
|
|
||||||
if (!value.getLabel().isEmpty()) {
|
|
||||||
mPaint.setStrokeWidth(0);
|
|
||||||
mPaint.setTextSize((float) (scaledTextSize * 1.5));
|
|
||||||
mPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
|
|
||||||
Rect bounds = new Rect();
|
|
||||||
mPaint.getTextBounds(value.getLabel(), 0, value.getLabel().length(), bounds);
|
|
||||||
mPaint.setStyle(Paint.Style.STROKE);
|
|
||||||
float py = graphTop + 80;
|
|
||||||
canvas.drawText(value.getLabel(), endX, py, mPaint);
|
|
||||||
mPaint.setStrokeWidth(5);
|
|
||||||
canvas.drawRect(endX - 3, bounds.top + py - 3, xPlusLength + 3, bounds.bottom + py + 3, mPaint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// set values above point
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* helper to render triangle
|
|
||||||
*
|
|
||||||
* @param point array with 3 coordinates
|
|
||||||
* @param canvas canvas to draw on
|
|
||||||
* @param paint paint object
|
|
||||||
*/
|
|
||||||
private void drawArrows(Point[] point, Canvas canvas, Paint paint) {
|
|
||||||
canvas.save();
|
|
||||||
Path path = new Path();
|
|
||||||
path.moveTo(point[0].x, point[0].y);
|
|
||||||
path.lineTo(point[1].x, point[1].y);
|
|
||||||
path.lineTo(point[2].x, point[2].y);
|
|
||||||
path.close();
|
|
||||||
canvas.drawPath(path, paint);
|
|
||||||
canvas.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawLabel45Right(float endX, float endY, E value, Canvas canvas, Float scaledPxSize, Float scaledTextSize) {
|
|
||||||
float py = endY - scaledPxSize;
|
|
||||||
canvas.save();
|
|
||||||
canvas.rotate(-45, endX, py);
|
|
||||||
mPaint.setTextSize((float) (scaledTextSize * 0.8));
|
|
||||||
mPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL));
|
|
||||||
mPaint.setFakeBoldText(true);
|
|
||||||
canvas.drawText(value.getLabel(), endX + scaledPxSize, py, mPaint);
|
|
||||||
canvas.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawLabel45Left(float endX, float endY, E value, Canvas canvas, Float scaledPxSize, Float scaledTextSize) {
|
|
||||||
float py = endY + scaledPxSize;
|
|
||||||
canvas.save();
|
|
||||||
canvas.rotate(-45, endX, py);
|
|
||||||
mPaint.setTextSize((float) (scaledTextSize * 0.8));
|
|
||||||
mPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL));
|
|
||||||
mPaint.setFakeBoldText(true);
|
|
||||||
mPaint.setTextAlign(Paint.Align.RIGHT);
|
|
||||||
canvas.drawText(value.getLabel(), endX - scaledPxSize, py, mPaint);
|
|
||||||
mPaint.setTextAlign(Paint.Align.LEFT);
|
|
||||||
canvas.restore();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,346 @@
|
||||||
|
package info.nightscout.core.graph.data
|
||||||
|
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.Path
|
||||||
|
import android.graphics.Point
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.jjoe64.graphview.GraphView
|
||||||
|
import com.jjoe64.graphview.series.BaseSeries
|
||||||
|
import info.nightscout.core.main.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Series that plots the data as points.
|
||||||
|
* The points can be different shapes or a
|
||||||
|
* complete custom drawing.
|
||||||
|
*
|
||||||
|
* @author jjoe64
|
||||||
|
*/
|
||||||
|
class PointsWithLabelGraphSeries<E : DataPointWithLabelInterface> : BaseSeries<E> {
|
||||||
|
|
||||||
|
// Default spSize
|
||||||
|
var spSize = 14
|
||||||
|
|
||||||
|
/**
|
||||||
|
* choose a predefined shape to render for
|
||||||
|
* each data point.
|
||||||
|
* You can also render a custom drawing via [com.jjoe64.graphview.series.PointsGraphSeries.CustomShape]
|
||||||
|
*/
|
||||||
|
enum class Shape {
|
||||||
|
|
||||||
|
BG,
|
||||||
|
PREDICTION,
|
||||||
|
TRIANGLE,
|
||||||
|
RECTANGLE,
|
||||||
|
BOLUS,
|
||||||
|
CARBS,
|
||||||
|
SMB,
|
||||||
|
EXTENDEDBOLUS,
|
||||||
|
PROFILE,
|
||||||
|
MBG,
|
||||||
|
BGCHECK,
|
||||||
|
ANNOUNCEMENT,
|
||||||
|
OPENAPS_OFFLINE,
|
||||||
|
EXERCISE,
|
||||||
|
GENERAL,
|
||||||
|
GENERAL_WITH_DURATION,
|
||||||
|
COB_FAIL_OVER,
|
||||||
|
IOB_PREDICTION,
|
||||||
|
BUCKETED_BG,
|
||||||
|
HEARTRATE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* internal paint object
|
||||||
|
*/
|
||||||
|
private lateinit var mPaint: Paint
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates the series without data
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates the series with data
|
||||||
|
*
|
||||||
|
* @param data dataPoints
|
||||||
|
*/
|
||||||
|
constructor(data: Array<E>?) : super(data) {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* init the internal objects
|
||||||
|
* set the defaults
|
||||||
|
*/
|
||||||
|
protected fun init() {
|
||||||
|
mPaint = Paint()
|
||||||
|
mPaint.strokeCap = Paint.Cap.ROUND
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* plot the data to the viewport
|
||||||
|
*
|
||||||
|
* @param graphView graphview
|
||||||
|
* @param canvas canvas to draw on
|
||||||
|
* @param isSecondScale whether it is the second scale
|
||||||
|
*/
|
||||||
|
@Suppress("deprecation")
|
||||||
|
override fun draw(graphView: GraphView, canvas: Canvas, isSecondScale: Boolean) {
|
||||||
|
// Convert the sp to pixels
|
||||||
|
val scaledTextSize = spSize * graphView.context.resources.displayMetrics.scaledDensity
|
||||||
|
val scaledPxSize = graphView.context.resources.displayMetrics.scaledDensity * 3f
|
||||||
|
resetDataPoints()
|
||||||
|
|
||||||
|
// get data
|
||||||
|
val maxX = graphView.viewport.getMaxX(false)
|
||||||
|
val minX = graphView.viewport.getMinX(false)
|
||||||
|
val maxY: Double
|
||||||
|
val minY: Double
|
||||||
|
if (isSecondScale) {
|
||||||
|
maxY = graphView.secondScale.maxY
|
||||||
|
minY = graphView.secondScale.minY
|
||||||
|
} else {
|
||||||
|
maxY = graphView.viewport.getMaxY(false)
|
||||||
|
minY = graphView.viewport.getMinY(false)
|
||||||
|
}
|
||||||
|
val values = getValues(minX, maxX)
|
||||||
|
|
||||||
|
// draw background
|
||||||
|
// draw data
|
||||||
|
val diffY = maxY - minY
|
||||||
|
val diffX = maxX - minX
|
||||||
|
val graphHeight = graphView.graphContentHeight.toFloat()
|
||||||
|
val graphWidth = graphView.graphContentWidth.toFloat()
|
||||||
|
val graphLeft = graphView.graphContentLeft.toFloat()
|
||||||
|
val graphTop = graphView.graphContentTop.toFloat()
|
||||||
|
val scaleX = (graphWidth / diffX).toFloat()
|
||||||
|
while (values.hasNext()) {
|
||||||
|
val value = values.next() ?: break
|
||||||
|
mPaint.color = value.color(graphView.context)
|
||||||
|
val valY = value.y - minY
|
||||||
|
val ratY = valY / diffY
|
||||||
|
val y = graphHeight * ratY
|
||||||
|
val valX = value.x - minX
|
||||||
|
val ratX = valX / diffX
|
||||||
|
var x = graphWidth * ratX
|
||||||
|
|
||||||
|
// overdraw
|
||||||
|
var overdraw = x > graphWidth
|
||||||
|
// end right
|
||||||
|
if (y < 0) { // end bottom
|
||||||
|
overdraw = true
|
||||||
|
}
|
||||||
|
if (y > graphHeight) { // end top
|
||||||
|
overdraw = true
|
||||||
|
}
|
||||||
|
val duration = value.duration
|
||||||
|
val endWithDuration = (x + duration * scaleX + graphLeft + 1).toFloat()
|
||||||
|
// cut off to graph start if needed
|
||||||
|
if (x < 0 && endWithDuration > 0) {
|
||||||
|
x = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fix a bug that continue to show the DOT after Y axis */if (x < 0) {
|
||||||
|
overdraw = true
|
||||||
|
}
|
||||||
|
val endX = x.toFloat() + (graphLeft + 1)
|
||||||
|
val endY = (graphTop - y).toFloat() + graphHeight
|
||||||
|
registerDataPoint(endX, endY, value)
|
||||||
|
var xPlusLength = 0f
|
||||||
|
if (duration > 0) {
|
||||||
|
xPlusLength = Math.min(endWithDuration, graphLeft + graphWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw data point
|
||||||
|
if (!overdraw) {
|
||||||
|
if (value.shape == Shape.BG || value.shape == Shape.COB_FAIL_OVER) {
|
||||||
|
mPaint.style = value.paintStyle
|
||||||
|
mPaint.strokeWidth = 0f
|
||||||
|
canvas.drawCircle(endX, endY, value.size * scaledPxSize, mPaint)
|
||||||
|
} else if (value.shape == Shape.BG || value.shape == Shape.IOB_PREDICTION || value.shape == Shape.BUCKETED_BG) {
|
||||||
|
mPaint.color = value.color(graphView.context)
|
||||||
|
mPaint.style = value.paintStyle
|
||||||
|
mPaint.strokeWidth = 0f
|
||||||
|
canvas.drawCircle(endX, endY, value.size * scaledPxSize, mPaint)
|
||||||
|
} else if (value.shape == Shape.PREDICTION) {
|
||||||
|
mPaint.color = value.color(graphView.context)
|
||||||
|
mPaint.style = value.paintStyle
|
||||||
|
mPaint.strokeWidth = 0f
|
||||||
|
canvas.drawCircle(endX, endY, scaledPxSize, mPaint)
|
||||||
|
mPaint.style = value.paintStyle
|
||||||
|
mPaint.strokeWidth = 0f
|
||||||
|
canvas.drawCircle(endX, endY, scaledPxSize / 3, mPaint)
|
||||||
|
} else if (value.shape == Shape.RECTANGLE) {
|
||||||
|
canvas.drawRect(endX - scaledPxSize, endY - scaledPxSize, endX + scaledPxSize, endY + scaledPxSize, mPaint)
|
||||||
|
} else if (value.shape == Shape.TRIANGLE) {
|
||||||
|
mPaint.strokeWidth = 0f
|
||||||
|
val points = arrayOf(
|
||||||
|
Point(endX.toInt(), (endY - scaledPxSize).toInt()),
|
||||||
|
Point((endX + scaledPxSize).toInt(), (endY + scaledPxSize * 0.67).toInt()),
|
||||||
|
Point((endX - scaledPxSize).toInt(), (endY + scaledPxSize * 0.67).toInt())
|
||||||
|
)
|
||||||
|
drawArrows(points, canvas, mPaint)
|
||||||
|
} else if (value.shape == Shape.BOLUS) {
|
||||||
|
mPaint.strokeWidth = 0f
|
||||||
|
val points = arrayOf(
|
||||||
|
Point(endX.toInt(), (endY - scaledPxSize).toInt()),
|
||||||
|
Point((endX + scaledPxSize).toInt(), (endY + scaledPxSize * 0.67).toInt()),
|
||||||
|
Point((endX - scaledPxSize).toInt(), (endY + scaledPxSize * 0.67).toInt())
|
||||||
|
)
|
||||||
|
mPaint.style = Paint.Style.FILL_AND_STROKE
|
||||||
|
drawArrows(points, canvas, mPaint)
|
||||||
|
if (value.label.isNotEmpty()) drawLabel45Right(endX, endY, value, canvas, scaledPxSize, scaledTextSize)
|
||||||
|
} else if (value.shape == Shape.CARBS) {
|
||||||
|
mPaint.strokeWidth = 0f
|
||||||
|
val points = arrayOf(
|
||||||
|
Point(endX.toInt(), (endY - scaledPxSize).toInt()),
|
||||||
|
Point((endX + scaledPxSize).toInt(), (endY + scaledPxSize * 0.67).toInt()),
|
||||||
|
Point((endX - scaledPxSize).toInt(), (endY + scaledPxSize * 0.67).toInt())
|
||||||
|
)
|
||||||
|
mPaint.style = Paint.Style.FILL_AND_STROKE
|
||||||
|
drawArrows(points, canvas, mPaint)
|
||||||
|
if (value.label.isNotEmpty()) drawLabel45Left(endX, endY, value, canvas, scaledPxSize, scaledTextSize)
|
||||||
|
} else if (value.shape == Shape.SMB) {
|
||||||
|
mPaint.strokeWidth = 2f
|
||||||
|
val size = value.size * scaledPxSize
|
||||||
|
val points = arrayOf(
|
||||||
|
Point(endX.toInt(), (endY - size).toInt()),
|
||||||
|
Point((endX + size).toInt(), (endY + size * 0.67).toInt()),
|
||||||
|
Point((endX - size).toInt(), (endY + size * 0.67).toInt())
|
||||||
|
)
|
||||||
|
mPaint.style = Paint.Style.FILL_AND_STROKE
|
||||||
|
drawArrows(points, canvas, mPaint)
|
||||||
|
} else if (value.shape == Shape.EXTENDEDBOLUS) {
|
||||||
|
mPaint.strokeWidth = 0f
|
||||||
|
if (value.label.isNotEmpty()) {
|
||||||
|
val bounds = Rect(endX.toInt(), endY.toInt() + 3, xPlusLength.toInt(), endY.toInt() + 8)
|
||||||
|
mPaint.style = Paint.Style.FILL_AND_STROKE
|
||||||
|
canvas.drawRect(bounds, mPaint)
|
||||||
|
mPaint.textSize = scaledTextSize
|
||||||
|
mPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL))
|
||||||
|
mPaint.isFakeBoldText = true
|
||||||
|
canvas.drawText(value.label, endX, endY, mPaint)
|
||||||
|
}
|
||||||
|
} else if (value.shape == Shape.HEARTRATE) {
|
||||||
|
mPaint.strokeWidth = 0f
|
||||||
|
val bounds = Rect(endX.toInt(), endY.toInt() - 8, xPlusLength.toInt(), endY.toInt() + 8)
|
||||||
|
mPaint.style = Paint.Style.FILL_AND_STROKE
|
||||||
|
canvas.drawRect(bounds, mPaint)
|
||||||
|
} else if (value.shape == Shape.PROFILE) {
|
||||||
|
val drawable = ContextCompat.getDrawable(graphView.context, R.drawable.ic_ribbon_profile) ?: break
|
||||||
|
drawable.setColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY)
|
||||||
|
drawable.setBounds(
|
||||||
|
(endX - drawable.intrinsicWidth / 2).toInt(),
|
||||||
|
(endY - drawable.intrinsicHeight / 2).toInt(),
|
||||||
|
(endX + drawable.intrinsicWidth / 2).toInt(),
|
||||||
|
(endY + drawable.intrinsicHeight / 2).toInt()
|
||||||
|
)
|
||||||
|
drawable.draw(canvas)
|
||||||
|
mPaint.textSize = scaledTextSize * 0.8f
|
||||||
|
mPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL))
|
||||||
|
mPaint.color = value.color(graphView.context)
|
||||||
|
val bounds = Rect()
|
||||||
|
mPaint.getTextBounds(value.label, 0, value.label.length, bounds)
|
||||||
|
val px = endX - bounds.width() / 2.0f
|
||||||
|
val py = endY + drawable.intrinsicHeight
|
||||||
|
mPaint.style = Paint.Style.FILL
|
||||||
|
canvas.drawText(value.label, px, py, mPaint)
|
||||||
|
} else if (value.shape == Shape.MBG) {
|
||||||
|
mPaint.style = Paint.Style.STROKE
|
||||||
|
mPaint.strokeWidth = 5f
|
||||||
|
canvas.drawCircle(endX, endY, scaledPxSize, mPaint)
|
||||||
|
} else if (value.shape == Shape.BGCHECK || value.shape == Shape.ANNOUNCEMENT || value.shape == Shape.GENERAL) {
|
||||||
|
mPaint.style = Paint.Style.FILL_AND_STROKE
|
||||||
|
mPaint.strokeWidth = 0f
|
||||||
|
canvas.drawCircle(endX, endY, scaledPxSize, mPaint)
|
||||||
|
if (value.label.isNotEmpty()) drawLabel45Right(endX, endY, value, canvas, scaledPxSize, scaledTextSize)
|
||||||
|
} else if (value.shape == Shape.EXERCISE) {
|
||||||
|
mPaint.strokeWidth = 0f
|
||||||
|
if (!value.label.isEmpty()) {
|
||||||
|
mPaint.strokeWidth = 0f
|
||||||
|
mPaint.textSize = (scaledTextSize * 1.2).toFloat()
|
||||||
|
mPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD))
|
||||||
|
val bounds = Rect()
|
||||||
|
mPaint.getTextBounds(value.label, 0, value.label.length, bounds)
|
||||||
|
mPaint.style = Paint.Style.STROKE
|
||||||
|
val py = graphTop + 20
|
||||||
|
canvas.drawText(value.label, endX, py, mPaint)
|
||||||
|
mPaint.strokeWidth = 5f
|
||||||
|
canvas.drawRect(endX - 3, bounds.top + py - 3, xPlusLength + 3, bounds.bottom + py + 3, mPaint)
|
||||||
|
}
|
||||||
|
} else if (value.shape == Shape.OPENAPS_OFFLINE && value.duration != 0L) {
|
||||||
|
mPaint.strokeWidth = 0f
|
||||||
|
if (!value.label.isEmpty()) {
|
||||||
|
mPaint.style = Paint.Style.FILL_AND_STROKE
|
||||||
|
mPaint.strokeWidth = 5f
|
||||||
|
canvas.drawRect(endX - 3, graphTop, xPlusLength + 3, graphTop + graphHeight, mPaint)
|
||||||
|
}
|
||||||
|
} else if (value.shape == Shape.GENERAL_WITH_DURATION) {
|
||||||
|
mPaint.strokeWidth = 0f
|
||||||
|
if (!value.label.isEmpty()) {
|
||||||
|
mPaint.strokeWidth = 0f
|
||||||
|
mPaint.textSize = (scaledTextSize * 1.5).toFloat()
|
||||||
|
mPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD))
|
||||||
|
val bounds = Rect()
|
||||||
|
mPaint.getTextBounds(value.label, 0, value.label.length, bounds)
|
||||||
|
mPaint.style = Paint.Style.STROKE
|
||||||
|
val py = graphTop + 80
|
||||||
|
canvas.drawText(value.label, endX, py, mPaint)
|
||||||
|
mPaint.strokeWidth = 5f
|
||||||
|
canvas.drawRect(endX - 3, bounds.top + py - 3, xPlusLength + 3, bounds.bottom + py + 3, mPaint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// set values above point
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* helper to render triangle
|
||||||
|
*
|
||||||
|
* @param point array with 3 coordinates
|
||||||
|
* @param canvas canvas to draw on
|
||||||
|
* @param paint paint object
|
||||||
|
*/
|
||||||
|
private fun drawArrows(point: Array<Point>, canvas: Canvas, paint: Paint) {
|
||||||
|
canvas.save()
|
||||||
|
val path = Path()
|
||||||
|
path.moveTo(point[0].x.toFloat(), point[0].y.toFloat())
|
||||||
|
path.lineTo(point[1].x.toFloat(), point[1].y.toFloat())
|
||||||
|
path.lineTo(point[2].x.toFloat(), point[2].y.toFloat())
|
||||||
|
path.close()
|
||||||
|
canvas.drawPath(path, paint)
|
||||||
|
canvas.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun drawLabel45Right(endX: Float, endY: Float, value: E, canvas: Canvas, scaledPxSize: Float, scaledTextSize: Float) {
|
||||||
|
val py = endY - scaledPxSize
|
||||||
|
canvas.save()
|
||||||
|
canvas.rotate(-45f, endX, py)
|
||||||
|
mPaint.textSize = (scaledTextSize * 0.8).toFloat()
|
||||||
|
mPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL))
|
||||||
|
mPaint.isFakeBoldText = true
|
||||||
|
canvas.drawText(value.label, endX + scaledPxSize, py, mPaint)
|
||||||
|
canvas.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun drawLabel45Left(endX: Float, endY: Float, value: E, canvas: Canvas, scaledPxSize: Float, scaledTextSize: Float) {
|
||||||
|
val py = endY + scaledPxSize
|
||||||
|
canvas.save()
|
||||||
|
canvas.rotate(-45f, endX, py)
|
||||||
|
mPaint.textSize = (scaledTextSize * 0.8).toFloat()
|
||||||
|
mPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL))
|
||||||
|
mPaint.isFakeBoldText = true
|
||||||
|
mPaint.textAlign = Paint.Align.RIGHT
|
||||||
|
canvas.drawText(value.label, endX - scaledPxSize, py, mPaint)
|
||||||
|
mPaint.textAlign = Paint.Align.LEFT
|
||||||
|
canvas.restore()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue