package org.wordpress.android.ui.stats; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Shader; import android.support.v4.view.GestureDetectorCompat; import android.view.GestureDetector; import android.view.MotionEvent; import com.jjoe64.graphview.CustomLabelFormatter; import com.jjoe64.graphview.GraphView; import com.jjoe64.graphview.GraphViewDataInterface; import com.jjoe64.graphview.GraphViewSeries.GraphViewSeriesStyle; import com.jjoe64.graphview.GraphViewStyle; import com.jjoe64.graphview.IndexDependentColor; import org.wordpress.android.R; import java.text.NumberFormat; import java.util.LinkedList; import java.util.List; /** * A Bar graph depicting the view and visitors. * Based on BarGraph from the GraphView library. */ class StatsBarGraph extends GraphView { private static final int DEFAULT_MAX_Y = 10; // Keep tracks of every bar drawn on the graph. private final List<List<BarChartRect>> mSeriesRectsDrawedOnScreen = (List<List<BarChartRect>>) new LinkedList(); private int mBarPositionToHighlight = -1; private boolean[] mWeekendDays; private final GestureDetectorCompat mDetector; private OnGestureListener mGestureListener; public StatsBarGraph(Context context) { super(context, ""); int width = LayoutParams.MATCH_PARENT; int height = getResources().getDimensionPixelSize(R.dimen.stats_barchart_height); setLayoutParams(new LayoutParams(width, height)); setProperties(); mDetector = new GestureDetectorCompat(getContext(), new MyGestureListener()); mDetector.setIsLongpressEnabled(false); } public void setGestureListener(OnGestureListener listener) { this.mGestureListener = listener; } class MyGestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDown(MotionEvent event) { return true; } @Override public boolean onSingleTapUp(MotionEvent event) { highlightBarAndBroadcastDate(); return false; } @Override public void onShowPress(MotionEvent e) { highlightBarAndBroadcastDate(); } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; } private void highlightBarAndBroadcastDate() { int tappedBar = getTappedBar(); //AppLog.d(AppLog.T.STATS, this.getClass().getName() + " Tapped bar " + tappedBar); if (tappedBar >= 0) { highlightBar(tappedBar); if (mGestureListener != null) { mGestureListener.onBarTapped(tappedBar); } } } } @Override public boolean onTouchEvent (MotionEvent event) { boolean handled = super.onTouchEvent(event); if (mDetector != null && handled) { this.mDetector.onTouchEvent(event); } return handled; } private class HorizontalLabelsColor implements IndexDependentColor { public int get(int index) { if (mBarPositionToHighlight == index) { return getResources().getColor(R.color.orange_jazzy); } else { return getResources().getColor(R.color.grey_darken_30); } } } private void setProperties() { GraphViewStyle gStyle = getGraphViewStyle(); gStyle.setHorizontalLabelsIndexDependentColor(new HorizontalLabelsColor()); gStyle.setHorizontalLabelsColor(getResources().getColor(R.color.grey_darken_30)); gStyle.setVerticalLabelsColor(getResources().getColor(R.color.grey_darken_10)); gStyle.setTextSize(getResources().getDimensionPixelSize(R.dimen.text_sz_extra_small)); gStyle.setGridXColor(Color.TRANSPARENT); gStyle.setGridYColor(getResources().getColor(R.color.grey_lighten_30)); gStyle.setNumVerticalLabels(3); setCustomLabelFormatter(new CustomLabelFormatter() { private NumberFormat numberFormatter; @Override public String formatLabel(double value, boolean isValueX) { if (isValueX) { return null; } if (numberFormatter == null) { numberFormatter = NumberFormat.getNumberInstance(); numberFormatter.setMaximumFractionDigits(0); } return numberFormatter.format(value); } }); } @Override protected void onBeforeDrawSeries() { mSeriesRectsDrawedOnScreen.clear(); } @Override public void drawSeries(Canvas canvas, GraphViewDataInterface[] values, float graphwidth, float graphheight, float border, double minX, double minY, double diffX, double diffY, float horstart, GraphViewSeriesStyle style) { float colwidth = graphwidth / values.length; int maxColumnSize = getGraphViewStyle().getMaxColumnWidth(); if (maxColumnSize > 0 && colwidth > maxColumnSize) { colwidth = maxColumnSize; } paint.setStrokeWidth(style.thickness); paint.setColor(style.color); // Bar chart position of this series on the canvas List<BarChartRect> barChartRects = new LinkedList<>(); // draw data for (int i = 0; i < values.length; i++) { float valY = (float) (values[i].getY() - minY); float ratY = (float) (valY / diffY); float y = graphheight * ratY; // hook for value dependent color if (style.getValueDependentColor() != null) { paint.setColor(style.getValueDependentColor().get(values[i])); } float pad = style.padding; float left = (i * colwidth) + horstart; float top = (border - y) + graphheight; float right = left + colwidth; float bottom = graphheight + border - 1; // Draw the orange selection behind the selected bar if (style.outerhighlightColor != 0x00ffffff && mBarPositionToHighlight == i) { paint.setColor(style.outerhighlightColor); canvas.drawRect(left, 10f, right, bottom, paint); } // Draw the grey background color on weekend days if (style.outerColor != 0x00ffffff && mBarPositionToHighlight != i && mWeekendDays != null && mWeekendDays[i]) { paint.setColor(style.outerColor); canvas.drawRect(left, 10f, right, bottom, paint); } if ((top - bottom) == 1) { // draw a placeholder if (mBarPositionToHighlight != i) { paint.setColor(style.color); paint.setAlpha(25); Shader shader = new LinearGradient(left + pad, bottom - 50, left + pad, bottom, Color.WHITE, Color.BLACK, Shader.TileMode.CLAMP); paint.setShader(shader); canvas.drawRect(left + pad, bottom - 50, right - pad, bottom, paint); paint.setShader(null); } } else { // draw a real bar paint.setAlpha(255); if (mBarPositionToHighlight == i) { paint.setColor(style.highlightColor); } else { paint.setColor(style.color); } canvas.drawRect(left + pad, top, right - pad, bottom, paint); } barChartRects.add(new BarChartRect(left + pad, top, right - pad, bottom)); } mSeriesRectsDrawedOnScreen.add(barChartRects); } private int getTappedBar() { float[] lastBarChartTouchedPoint = this.getLastTouchedPointOnCanvasAndReset(); if (lastBarChartTouchedPoint[0] == 0f && lastBarChartTouchedPoint[1] == 0f) { return -1; } for (List<BarChartRect> currentSerieChartRects : mSeriesRectsDrawedOnScreen) { int i = 0; for (BarChartRect barChartRect : currentSerieChartRects) { if (barChartRect.isPointInside(lastBarChartTouchedPoint[0], lastBarChartTouchedPoint[1])) { return i; } i++; } } return -1; } /* public float getMiddlePointOfTappedBar(int tappedBar) { if (tappedBar == -1 || mSeriesRectsDrawedOnScreen == null || mSeriesRectsDrawedOnScreen.size() == 0) { return -1; } BarChartRect rect = mSeriesRectsDrawedOnScreen.get(0).get(tappedBar); return ((rect.mLeft + rect.mRight) / 2) + getCanvasLeft(); } public void highlightAndDismissBar(int barPosition) { mBarPositionToHighlight = barPosition; if (mBarPositionToHighlight == -1) { return; } this.redrawAll(); final Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { mBarPositionToHighlight = -1; redrawAll(); } }, 500); } */ public void setWeekendDays(boolean[] days) { mWeekendDays = days; } public void highlightBar(int barPosition) { mBarPositionToHighlight = barPosition; this.redrawAll(); } public int getHighlightBar() { return mBarPositionToHighlight; } public void resetHighlightBar() { mBarPositionToHighlight = -1; } @Override protected double getMinY() { return 0; } // Make sure the highest number is always even, so the halfway mark is correctly balanced in the middle of the graph // Also make sure to display a default value when there is no activity in the period. @Override protected double getMaxY() { double maxY = super.getMaxY(); if (maxY == 0) { return DEFAULT_MAX_Y; } return maxY + (maxY % 2); } /** * Private class that is used to hold the local (to the canvas) coordinate on the screen * of every single bar in the graph */ private class BarChartRect { final float mLeft; final float mTop; final float mRight; final float mBottom; BarChartRect(float left, float top, float right, float bottom) { this.mLeft = left; this.mTop = top; this.mRight = right; this.mBottom = bottom; } /** * Check if the tap happens on a bar in the graph. * * @return true if the tap point falls within the bar for the X coordinate, and within the full canvas * height for the Y coordinate. This is a fix to make very small bars tappable. */ public boolean isPointInside(float x, float y) { return x >= this.mLeft && x <= this.mRight; } } interface OnGestureListener { void onBarTapped(int tappedBar); } }