-
-
Save MehdiFal/e2c960010e2becbd8ae177b95789e69c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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