Skip to content

Instantly share code, notes, and snippets.

@MehdiFal
Last active February 27, 2020 14:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save MehdiFal/e2c960010e2becbd8ae177b95789e69c to your computer and use it in GitHub Desktop.
Save MehdiFal/e2c960010e2becbd8ae177b95789e69c to your computer and use it in GitHub Desktop.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.test.graphvisualizer.Graph
android:id="@+id/graph"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:paddingLeft="35dp"
android:paddingStart="35dp"
android:paddingRight="35dp"
android:paddingEnd="35dp"
android:paddingTop="100dp"
android:paddingBottom="100dp"
app:gradientColorStart="@color/greenDark"
app:gradientColorEnd="@color/whiteTransparent"
app:gradientAlpha="0.85"
app:markerColor="@color/greenDark"
app:markerRadius="3dp"
app:connectLinesColor="@color/greenDark"
app:connectLinesWidth="1dp"
app:guidelinesFilledColor="@color/gray"
app:guidelinesFilledHeight="5dp"
app:guidelinesEmptyHeight="5dp"
app:guidelinesWidth="1dp"
app:graduationTextColor="@color/gray"
app:graduationTextSize="12sp"
app:xGroupHeight="20dp"
app:xGroupTextColor="@color/gray"
app:xGroupTextSize="12sp"
app:xGroupBgColor="@color/lightGray"
app:xGroupBorderColor="@color/gray"
app:xGroupBorderSize="1dp"
app:xGroupCornerRadius="20dp"/>
</android.support.constraint.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Graph">
<attr name="gradientColorStart" format="color"/>
<attr name="gradientColorEnd" format="color"/>
<attr name="gradientAlpha" format="float"/>
<attr name="markerColor" format="color"/>
<attr name="markerRadius" format="dimension" />
<attr name="connectLinesColor" format="color"/>
<attr name="connectLinesWidth" format="dimension" />
<attr name="guidelinesFilledColor" format="color"/>
<attr name="guidelinesFilledHeight" format="dimension" />
<attr name="guidelinesEmptyHeight" format="dimension" />
<attr name="guidelinesWidth" format="dimension"/>
<attr name="graduationTextColor" format="color"/>
<attr name="graduationTextSize" format="dimension" />
<attr name="xGroupTextColor" format="color" />
<attr name="xGroupTextSize" format="dimension" />
<attr name="xGroupBgColor" format="color" />
<attr name="xGroupBorderColor" format="color" />
<attr name="xGroupBorderSize" format="dimension" />
<attr name="xGroupCornerRadius" format="dimension" />
<attr name="xGroupHeight" format="dimension" />
</declare-styleable>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
<color name="whiteTransparent">#00FFFFFF</color>
<color name="greenDark">#FF00897B</color>
<color name="greenLight">#FFE0F2F1</color>
<color name="gray">#c1c1c1</color>
<color name="lightGray">#80e9e9e9</color>
</resources>
package com.test.graphvisualizer;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.DashPathEffect;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Shader;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import com.test.graphvisualizer.models.Marker;
import java.util.List;
public class Graph extends View {
interface Defaults {
int Color = R.color.colorPrimaryDark;
int LightColor = R.color.colorPrimary;
int TextColor = R.color.gray;
float Opacity = 0.8f;
int TextSp = 12;
int MarkerRadiusDP = 5;
int GuidelineHeightDp = 5;
int LinesWidthDp = 2;
int RoundCornerRadiusDp = 2;
int XGroupHeightDp = 20;
int PadBottomOffsetDp = 15;
int PadSidesBottomOffsetDp = 5;
}
private Paint paint;
private Paint textPaint;
private Paint gradientPaint;
private Paint linesPaint;
private Path path;
private int markerColor;
private float markerRadius;
private int connectLinesColor;
private float connectLinesWidth;
private int guidelinesFilledColor;
private float guidelinesFilledHeight;
private float guidelinesEmptyHeight;
private float guidelinesWidth;
private int graduationTextColor;
private float graduationTextSize;
private int gradientColorStart;
private int gradientColorEnd;
private float gradientAlpha;
private float xGroupHeight;
private int xGroupTextColor;
private float xGroupTextSize;
private int xGroupBgColor;
private int xGroupBorderColor;
private float xGroupBorderSize;
private float xGroupCornerRadius;
private int maxY;
private int graphHeight;
private int paddingBottomOffset;
private int weeksCount;
private int weekWidth;
private int lastWeekMarkers;
private int maxYGraduation;
private float yScaleUnit;
private List<Marker> markers;
public Graph (Context context) {
super (context);
init (context, null, 0, 0);
}
public Graph (Context context, @Nullable AttributeSet attrs) {
super (context, attrs);
init (context, attrs, 0, 0);
}
public Graph (Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super (context, attrs, defStyleAttr);
init (context, attrs, defStyleAttr, 0);
}
@RequiresApi (api = Build.VERSION_CODES.LOLLIPOP)
public Graph (Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super (context, attrs, defStyleAttr, defStyleRes);
init (context, attrs, defStyleAttr, defStyleRes);
}
private void init (Context context, AttributeSet attrs, int defStyle, int defStyleRes) {
path = new Path ();
final TypedArray typedArray = context.obtainStyledAttributes (attrs, R.styleable.Graph, defStyle, defStyleRes);
initAttributes (context, typedArray);
paint = new Paint (Paint.ANTI_ALIAS_FLAG);
textPaint = new TextPaint (Paint.ANTI_ALIAS_FLAG);
gradientPaint = new Paint (Paint.ANTI_ALIAS_FLAG);
gradientPaint.setStyle (Paint.Style.FILL);
gradientPaint.setAlpha ((int) (gradientAlpha * 255)); // [0, 255]
linesPaint = new Paint (Paint.ANTI_ALIAS_FLAG);
linesPaint.setColor (connectLinesColor);
linesPaint.setStrokeWidth (connectLinesWidth);
typedArray.recycle ();
}
private void initAttributes (Context context, TypedArray typedArray) {
Resources res = context.getResources ();
int defColor = res.getColor (Defaults.Color);
int defLightColor = res.getColor (Defaults.LightColor);
int defTextColor = res.getColor (Defaults.TextColor);
int defTextSize = spToPx (res, Defaults.TextSp);
int defLinesWidth = dpToPx (res, Defaults.LinesWidthDp);
markerColor = typedArray.getColor (R.styleable.Graph_markerColor, defColor);
markerRadius = typedArray.getDimension (R.styleable.Graph_markerRadius, dpToPx (res, Defaults.MarkerRadiusDP));
connectLinesColor = typedArray.getColor (R.styleable.Graph_connectLinesColor, defColor);
connectLinesWidth = typedArray.getDimension (R.styleable.Graph_connectLinesWidth, defLinesWidth);
int defGuidelineDim = dpToPx (res, Defaults.GuidelineHeightDp);
guidelinesFilledHeight = typedArray.getDimension (R.styleable.Graph_guidelinesFilledHeight, defGuidelineDim);
guidelinesEmptyHeight = typedArray.getDimension (R.styleable.Graph_guidelinesEmptyHeight, defGuidelineDim);
guidelinesFilledColor = typedArray.getColor (R.styleable.Graph_guidelinesFilledColor, defColor);
guidelinesWidth = typedArray.getDimension (R.styleable.Graph_guidelinesWidth, defLinesWidth);
graduationTextColor = typedArray.getColor (R.styleable.Graph_graduationTextColor, defTextColor);
graduationTextSize = typedArray.getDimension (R.styleable.Graph_graduationTextSize, defTextSize);
gradientColorStart = typedArray.getColor (R.styleable.Graph_gradientColorStart, defColor);
gradientColorEnd = typedArray.getColor (R.styleable.Graph_gradientColorEnd, defLightColor);
gradientAlpha = typedArray.getFloat (R.styleable.Graph_gradientAlpha, Defaults.Opacity);
xGroupHeight = typedArray.getDimension (R.styleable.Graph_xGroupHeight, dpToPx (res, Defaults.XGroupHeightDp));
xGroupTextColor = typedArray.getColor (R.styleable.Graph_xGroupTextColor, defTextColor);
xGroupTextSize = typedArray.getDimension (R.styleable.Graph_xGroupTextSize, defTextSize);
xGroupBgColor = typedArray.getColor (R.styleable.Graph_xGroupBgColor, defLightColor);
xGroupBorderColor = typedArray.getColor (R.styleable.Graph_xGroupBorderColor, defColor);
xGroupBorderSize = typedArray.getDimension (R.styleable.Graph_xGroupBorderSize, defLinesWidth);
xGroupCornerRadius = typedArray.getDimension (R.styleable.Graph_xGroupCornerRadius, dpToPx (res, Defaults.RoundCornerRadiusDp));
}
@Override
protected void onSizeChanged (int w, int h, int oldw, int oldh) {
super.onSizeChanged (w, h, oldw, oldh);
paddingBottomOffset = dpToPx (getResources (), Defaults.PadBottomOffsetDp);
int paddingBottom = (int) (getPaddingBottom () + xGroupHeight + paddingBottomOffset);
maxY = getHeight () - paddingBottom;
weekWidth = (getWidth () - getPaddingLeft () - getPaddingRight ()) / weeksCount;
graphHeight = getHeight () - getPaddingTop () - paddingBottom;
initGradientPaint ();
}
@Override
protected void onDraw (Canvas canvas) {
super.onDraw (canvas);
if (markers == null) {
return;
}
drawSkeleton (canvas);
colorAreaBelowPoints (canvas);
drawGraduations (canvas);
drawConnectedPoints (canvas);
drawXGroups (canvas);
}
private void drawSkeleton (Canvas canvas) {
initSkeletonPaint ();
int x = getPaddingLeft ();
for (int i = 0; i < weeksCount; i ++) {
path.moveTo (x, getPaddingTop ());
path.lineTo (x, maxY);
canvas.drawPath (path, paint);
path.reset ();
x += weekWidth;
}
path.reset ();
paint.reset ();
}
private void initSkeletonPaint () {
paint.setStyle (Paint.Style.STROKE);
paint.setStrokeWidth (guidelinesWidth);
paint.setColor (guidelinesFilledColor);
int numParts = (int) Math.floor (graphHeight / (guidelinesFilledHeight + guidelinesEmptyHeight));
float [] interval = new float [numParts];
for (int i = 0; i < interval.length; i ++) {
interval [i] = (i % 2 == 0) ? guidelinesFilledHeight : guidelinesEmptyHeight;
}
paint.setPathEffect (new DashPathEffect (interval, 0f));
}
private void colorAreaBelowPoints (Canvas canvas) {
path.moveTo (getPaddingLeft (), maxY);
float centerX = getPaddingLeft ();
float distBetweenPoints = ((float) weekWidth) / 7;
for (Marker marker : markers) {
float centerY = maxY - (marker.getValue () * graphHeight) / maxYGraduation;
path.lineTo (centerX, centerY);
centerX += distBetweenPoints;
}
Marker firstMarker = markers.get (0);
float firstY = maxY - (firstMarker.getValue () * graphHeight) / maxYGraduation;
float lastX = centerX - distBetweenPoints;
path.lineTo (lastX, maxY);
path.lineTo (getPaddingLeft (), firstY);
canvas.drawPath (path, gradientPaint);
path.reset ();
}
private void drawGraduations (Canvas canvas) {
initGraduationsPaint ();
lastWeekMarkers = 0;
for (int i = markers.size () - 1; i >= 0; i --) {
Marker marker = markers.get (i);
if (marker.getWeek () < weeksCount) {
break;
}
lastWeekMarkers ++;
}
int x = getWidth () - getPaddingRight () - (weekWidth / lastWeekMarkers);
float distY = (yScaleUnit * graphHeight) / maxYGraduation;
float y = maxY;
for (int i = 0; i <= maxYGraduation; i += yScaleUnit) {
canvas.drawText (String.valueOf (i), x, y, textPaint);
y -= distY;
}
path.reset ();
textPaint.reset ();
}
private void initGraduationsPaint () {
textPaint.setColor (graduationTextColor);
textPaint.setTextAlign (Paint.Align.LEFT);
textPaint.setTextSize (graduationTextSize);
}
private void drawConnectedPoints (Canvas canvas) {
initPointsPaint ();
float distBetweenPoints = ((float) weekWidth) / 7;
float centerX = getPaddingLeft ();
float prevX = -1f;
float prevY = -1f;
for (Marker marker : markers) {
float centerY = maxY - (marker.getValue () * graphHeight) / maxYGraduation;
if (prevX != -1f) {
canvas.drawLine (prevX, prevY, centerX, centerY, linesPaint);
}
prevX = centerX;
prevY = centerY;
canvas.drawCircle (centerX, centerY, markerRadius, paint);
centerX += distBetweenPoints;
}
linesPaint.reset ();
paint.reset ();
}
private void initPointsPaint () {
paint.setStyle (Paint.Style.FILL);
paint.setStrokeWidth (markerRadius);
paint.setColor (markerColor);
}
private void drawXGroups (Canvas canvas) {
initXGroupsPaint ();
int sidePadding = dpToPx (getResources (), Defaults.PadSidesBottomOffsetDp);
RectF rect = new RectF ();
int x = getPaddingLeft ();
int yTop = maxY + paddingBottomOffset;
float yBottom = yTop + xGroupHeight;
for (int i = 0; i < weeksCount; i ++) {
String text;
float xRight;
if (i == (weeksCount - 1) && lastWeekMarkers < 7) {
xRight = x + (weekWidth / (lastWeekMarkers - 1));
text = "+" + lastWeekMarkers;
} else {
xRight = x + weekWidth - sidePadding;
text = "Week " + (i + 1);
}
rect.set (x + sidePadding, yTop, xRight, yBottom);
canvas.drawRoundRect (rect, xGroupCornerRadius, xGroupCornerRadius, linesPaint);
canvas.drawRoundRect (rect, xGroupCornerRadius, xGroupCornerRadius, paint);
float xText = rect.centerX ();
float yText = rect.centerY () - ((textPaint.descent () + textPaint.ascent ()) / 2);
canvas.drawText (text, xText, yText, textPaint);
x += weekWidth;
}
linesPaint.reset ();
textPaint.reset ();
paint.reset ();
}
private void initXGroupsPaint () {
paint.setStyle (Paint.Style.FILL);
paint.setColor (xGroupBgColor);
paint.setStrokeWidth (xGroupBorderSize);
linesPaint.setStyle (Paint.Style.STROKE);
linesPaint.setStrokeWidth (xGroupBorderSize);
linesPaint.setColor (xGroupBorderColor);
textPaint.setColor (xGroupTextColor);
textPaint.setTextAlign (Paint.Align.CENTER);
textPaint.setTextSize (xGroupTextSize);
}
private void initGradientPaint () {
LinearGradient gradient = new LinearGradient (
getPaddingLeft (),
getPaddingTop (),
getPaddingRight (),
maxY,
new int [] { gradientColorStart, gradientColorEnd },
null,
Shader.TileMode.CLAMP
);
gradientPaint.setShader (gradient);
}
public void setData (List<Marker> markers, int yScaleUnit) {
this.markers = markers;
this.yScaleUnit = yScaleUnit;
int maxY = maxValue (markers);
maxYGraduation = ((maxY / yScaleUnit) + 1) * yScaleUnit;
for (Marker marker : markers) {
weeksCount = Math.max (weeksCount, marker.getWeek ());
}
invalidate ();
}
private int maxValue (List<Marker> markers) {
int maxValue = 0;
for (Marker marker : markers) {
maxValue = Math.max (maxValue, marker.getValue ());
}
return maxValue;
}
private static int spToPx (Resources res, float sp) {
return (int) TypedValue.applyDimension (TypedValue.COMPLEX_UNIT_SP, sp, res.getDisplayMetrics ());
}
private static int dpToPx (Resources res, float dp) {
return (int) TypedValue.applyDimension (TypedValue.COMPLEX_UNIT_DIP, dp, res.getDisplayMetrics ());
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate (savedInstanceState);
setContentView (R.layout.activity_main);
Graph graph = findViewById (R.id.graph);
List<Marker> markers = markers ();
graph.setData (markers, 100);
}
private List<Marker> markers () {
List<Marker> markers = new ArrayList<> ();
markers.add (new Marker (0, 1, 1, 1));
markers.add (new Marker (200, 2, 1, 1));
markers.add (new Marker (100, 3, 1, 1));
markers.add (new Marker (150, 4, 1, 1));
markers.add (new Marker (50, 5, 1, 1));
markers.add (new Marker (0, 6, 1, 1));
markers.add (new Marker (350, 7, 1, 1));
markers.add (new Marker (320, 1, 2, 1));
markers.add (new Marker (320, 2, 2, 1));
markers.add (new Marker (275, 3, 2, 1));
markers.add (new Marker (0, 4, 2, 1));
markers.add (new Marker (0, 5, 2, 1));
markers.add (new Marker (200, 6, 2, 1));
markers.add (new Marker (30, 7, 2, 1));
markers.add (new Marker (550, 1, 3, 1));
markers.add (new Marker (80, 2, 3, 1));
markers.add (new Marker (110, 3, 3, 1));
markers.add (new Marker (50, 4, 3, 1));
markers.add (new Marker (50, 5, 3, 1));
markers.add (new Marker (80, 6, 3, 1));
markers.add (new Marker (50, 7, 3, 1));
markers.add (new Marker (0, 1, 4, 1));
markers.add (new Marker (30, 2, 4, 1));
markers.add (new Marker (70, 3, 4, 1));
markers.add (new Marker (110, 4, 4, 1));
markers.add (new Marker (70, 5, 4, 1));
markers.add (new Marker (0, 6, 4, 1));
markers.add (new Marker (30, 7, 4, 1));
markers.add (new Marker (70, 1, 5, 1));
markers.add (new Marker (130, 2, 5, 1));
markers.add (new Marker (250, 3, 5, 1));
return markers;
}
}
public class Marker {
private int value;
private int day;
private int week;
private int month;
public Marker (int value, int day, int week, int month) {
this.value = value;
this.day = day;
this.week = week;
this.month = month;
}
public int getValue () {
return value;
}
public void setValue (int value) {
this.value = value;
}
public int getDay () {
return day;
}
public void setDay (int day) {
this.day = day;
}
public int getWeek () {
return week;
}
public void setWeek (int week) {
this.week = week;
}
public int getMonth () {
return month;
}
public void setMonth (int month) {
this.month = month;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment