|
|
@@ -0,0 +1,864 @@
|
|
|
+package hr.tacitus.graphs;
|
|
|
+
|
|
|
+import android.content.Context;
|
|
|
+import android.content.res.TypedArray;
|
|
|
+import android.graphics.Canvas;
|
|
|
+import android.graphics.Color;
|
|
|
+import android.graphics.Paint;
|
|
|
+import android.graphics.Rect;
|
|
|
+import android.util.AttributeSet;
|
|
|
+import android.util.DisplayMetrics;
|
|
|
+import android.view.Display;
|
|
|
+import android.view.GestureDetector;
|
|
|
+import android.view.GestureDetector.OnGestureListener;
|
|
|
+import android.view.MotionEvent;
|
|
|
+import android.view.View;
|
|
|
+import android.view.WindowManager;
|
|
|
+
|
|
|
+import hr.tacitus.graphs.R;
|
|
|
+
|
|
|
+public class BarGraphView extends View {
|
|
|
+ // Spacing between axis and values, and values and description.
|
|
|
+ private final static float DEFAULT_AXIS_LINE_SPACING_DP = 2.0f;
|
|
|
+ private final static float DEFAULT_MARKINGS_LENGTH_DP = 6.0f;
|
|
|
+ private final static float DEFAULT_AXIS_DESCRIPTION_FONT_SIZE_SP = 16.f;
|
|
|
+ private final static float AVERAGE_LINE_STROKE_WIDTH = 1.f;
|
|
|
+ private final static float VALUE_TEXT_OFFSET = 5f;
|
|
|
+
|
|
|
+ VerticalAxisRect verticalAxisRect = new VerticalAxisRect();
|
|
|
+ HorizontalAxisRect horizontalAxisRect = new HorizontalAxisRect();
|
|
|
+ BarConfig barConfig = new BarConfig();
|
|
|
+
|
|
|
+ private double maxGraphValue;
|
|
|
+ // Scroll value, default is leftmost.
|
|
|
+ private float offsetX = 0;
|
|
|
+ private boolean drawAverageLine = false;
|
|
|
+
|
|
|
+ private Paint averageLinePaint = null;
|
|
|
+ private Paint canvasPaint = null;
|
|
|
+ private Paint axisPaint = null;
|
|
|
+
|
|
|
+ private DisplayMetrics dm = null;
|
|
|
+
|
|
|
+ private BarGraphAdapter adapter;
|
|
|
+
|
|
|
+ // Paramaters
|
|
|
+ float barFontSizePx;
|
|
|
+ // Font size for each value
|
|
|
+ float axisValuesFontSizePx;
|
|
|
+ // Text padding on bars
|
|
|
+ float barPaddingPx;
|
|
|
+ float ordinateMinMarkingsDistance;
|
|
|
+
|
|
|
+ public BarGraphView(Context context) {
|
|
|
+ super(context);
|
|
|
+ init(context, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ public BarGraphView(Context context, AttributeSet attrs) {
|
|
|
+ super(context, attrs);
|
|
|
+ init(context, attrs);
|
|
|
+ }
|
|
|
+
|
|
|
+ public BarGraphView(Context context, AttributeSet attrs, int defStyle) {
|
|
|
+ super(context, attrs, defStyle);
|
|
|
+ init(context, attrs);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void setDefaultParamValues() {
|
|
|
+ barFontSizePx = getDimensionFromSp(15.f);
|
|
|
+ barPaddingPx = getDimensionFromDp(6.f);
|
|
|
+ axisValuesFontSizePx = getDimensionFromSp(14.f);
|
|
|
+ ordinateMinMarkingsDistance = getDimensionFromDp(40.f);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void setupDisplayMetrics() {
|
|
|
+ dm = new DisplayMetrics();
|
|
|
+ WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
|
|
|
+ Display d = wm.getDefaultDisplay();
|
|
|
+ d.getMetrics(dm);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void readAttributes(AttributeSet attrs) {
|
|
|
+ TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.BarGraph);
|
|
|
+ if (a != null) {
|
|
|
+ try {
|
|
|
+ String xAxisText = a.getString(R.styleable.BarGraph_xAxisText);
|
|
|
+ if (xAxisText != null) {
|
|
|
+ setDescriptionX(xAxisText);
|
|
|
+ }
|
|
|
+
|
|
|
+ String yAxisText = a.getString(R.styleable.BarGraph_yAxisText);
|
|
|
+ if (yAxisText != null) {
|
|
|
+ setDescriptionY(yAxisText);
|
|
|
+ }
|
|
|
+
|
|
|
+ barFontSizePx = a.getDimension(R.styleable.BarGraph_barTextSize, barFontSizePx);
|
|
|
+ barPaddingPx = a.getDimension(R.styleable.BarGraph_barPadding, barPaddingPx);
|
|
|
+ axisValuesFontSizePx = a.getDimension(R.styleable.BarGraph_axisValuesTextSize, axisValuesFontSizePx);
|
|
|
+
|
|
|
+ drawAverageLine = a.getBoolean(R.styleable.BarGraph_showAverageLine, drawAverageLine);
|
|
|
+
|
|
|
+ horizontalAxisRect.setShowValues(a.getBoolean(R.styleable.BarGraph_showHorizontalValues, false));
|
|
|
+ verticalAxisRect.setShowValues(a.getBoolean(R.styleable.BarGraph_showVerticalValues, true));
|
|
|
+
|
|
|
+ } finally {
|
|
|
+ a.recycle();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void cacheValues() {
|
|
|
+ // Initialize paints used in onDraw
|
|
|
+ averageLinePaint = new Paint();
|
|
|
+ averageLinePaint.setColor(Color.RED);
|
|
|
+ averageLinePaint.setStrokeWidth(getDimensionFromDp(AVERAGE_LINE_STROKE_WIDTH));
|
|
|
+
|
|
|
+ canvasPaint = new Paint();
|
|
|
+ canvasPaint.setStyle(Paint.Style.FILL);
|
|
|
+ canvasPaint.setColor(Color.WHITE);
|
|
|
+ canvasPaint.setAntiAlias(true);
|
|
|
+
|
|
|
+ axisPaint = new Paint();
|
|
|
+ axisPaint.setStyle(Paint.Style.FILL);
|
|
|
+ axisPaint.setColor(Color.BLACK);
|
|
|
+ axisPaint.setAntiAlias(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void init(Context context, AttributeSet attrs) {
|
|
|
+ setupDisplayMetrics();
|
|
|
+ setDefaultParamValues();
|
|
|
+ if (attrs != null) {
|
|
|
+ readAttributes(attrs);
|
|
|
+ }
|
|
|
+
|
|
|
+ cacheValues();
|
|
|
+
|
|
|
+ verticalAxisValues.setMinDistanceBetweenMarkings(ordinateMinMarkingsDistance);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void drawHorizontalAxis(Canvas canvas) {
|
|
|
+ canvas.drawLine(verticalAxisRect.getWidth() + getPaddingLeft(), horizontalAxisRect.getAxisBottom(),
|
|
|
+ getWidth() - getPaddingRight(), horizontalAxisRect.getAxisBottom(), axisPaint);
|
|
|
+ drawHorizontalMeasures(canvas);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void drawVerticalAxis(Canvas canvas) {
|
|
|
+ canvas.drawLine(verticalAxisRect.getWidth() + getPaddingLeft(), getPaddingTop(),
|
|
|
+ verticalAxisRect.getWidth() + getPaddingLeft(), horizontalAxisRect.getAxisBottom(), axisPaint);
|
|
|
+ drawVerticalMeasures(canvas);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void drawAverageLine(Canvas canvas) {
|
|
|
+ int width = getWidth();
|
|
|
+
|
|
|
+ double barHeight = getBarHeight(verticalAxisValues.getAverageValue());
|
|
|
+ float y1 = (float) (getPaddingTop() + getGraphHeight() - barHeight);
|
|
|
+ float y2 = y1;
|
|
|
+ canvas.drawLine(verticalAxisRect.getWidth() + getPaddingLeft(), y1, width - getPaddingRight(), y2, averageLinePaint);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void onDraw(Canvas canvas) {
|
|
|
+ super.onDraw(canvas);
|
|
|
+
|
|
|
+ if (adapter == null)
|
|
|
+ return;
|
|
|
+
|
|
|
+ canvas.drawPaint(canvasPaint);
|
|
|
+
|
|
|
+ drawHorizontalAxis(canvas);
|
|
|
+ drawVerticalAxis(canvas);
|
|
|
+ drawValues(canvas);
|
|
|
+ if (drawAverageLine) {
|
|
|
+ drawAverageLine(canvas);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void drawHorizontalMeasures(Canvas canvas) {
|
|
|
+ float xStart = verticalAxisRect.getWidth() + getPaddingLeft();
|
|
|
+ float barWidth = barConfig.getBarWidth();
|
|
|
+
|
|
|
+ float y1 = horizontalAxisRect.getAxisBottom();
|
|
|
+ float y2 = y1 + horizontalAxisRect.getAxisMarkingsLength();
|
|
|
+
|
|
|
+ for (int i = 0; i < adapter.getCount(); ++i) {
|
|
|
+ float x1 = xStart - offsetX + barConfig.getWhiteSpace() - getDimensionFromDp(1.0f);
|
|
|
+ float x2 = x1;
|
|
|
+
|
|
|
+ if (x1 <= getWidth() - getPaddingRight()) {
|
|
|
+ if (x1 >= verticalAxisRect.getWidth() + getPaddingLeft()) {
|
|
|
+ canvas.drawLine(x1, y1, x2, y2, horizontalAxisRect.getPaintLine());
|
|
|
+ if (horizontalAxisRect.isShowValues()) {
|
|
|
+ String horizontalDescription = adapter.getHorizontalValueDescription(i);
|
|
|
+ if (horizontalDescription != null) {
|
|
|
+ float textWidth = horizontalAxisRect.getPaintLine().measureText(horizontalDescription);
|
|
|
+ canvas.drawText(horizontalDescription, x1 - (textWidth / 2), horizontalAxisRect.getMarkingValuesY(), horizontalAxisRect.getPaintLine());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ xStart += barWidth + barConfig.getWhiteSpace();
|
|
|
+ }
|
|
|
+ if (horizontalAxisRect.getDescription() != null) {
|
|
|
+ float textWidth = horizontalAxisRect.getPaintDescription().measureText(horizontalAxisRect.getDescription());
|
|
|
+ float x = BarGraphView.this.getWidth() / 2 - textWidth / 2;
|
|
|
+ float y = horizontalAxisRect.getDescriptionY();
|
|
|
+ canvas.drawText(horizontalAxisRect.getDescription(), x, y, horizontalAxisRect.getPaintDescription());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void drawVerticalMeasures(Canvas canvas) {
|
|
|
+ if (adapter == null || adapter.getCount() == 0)
|
|
|
+ return;
|
|
|
+
|
|
|
+ initVerticalAxisValues();
|
|
|
+
|
|
|
+ float yStart = horizontalAxisRect.getAxisBottom();
|
|
|
+ float x1 = verticalAxisRect.getWidth() + getPaddingLeft();
|
|
|
+ float x2 = x1 - verticalAxisRect.getAxisMarkingsLength();
|
|
|
+ for (int i = 0; i <= verticalAxisValues.getMarkingsCount(); ++i) {
|
|
|
+ float y1 = yStart;
|
|
|
+ float y2 = y1;
|
|
|
+
|
|
|
+ canvas.drawLine(x1, y1, x2, y2, verticalAxisRect.getPaintLine());
|
|
|
+
|
|
|
+ if (verticalAxisRect.isShowValues()) {
|
|
|
+ // draw bounding rect before rotating text
|
|
|
+ Rect rect = new Rect();
|
|
|
+ canvas.save();
|
|
|
+
|
|
|
+ String markingText = adapter.getVerticalMarkingText(i * verticalAxisValues.getYieldBetweenMarkings());
|
|
|
+ float textWidth = verticalAxisRect.getPaintLine().measureText(markingText);
|
|
|
+
|
|
|
+ canvas.rotate(-90.f, rect.exactCenterX(), yStart + rect.exactCenterY());
|
|
|
+ canvas.drawText(markingText, -(textWidth / 2), yStart + verticalAxisRect.getValuesX() + getPaddingLeft(), verticalAxisRect.getPaintLine());
|
|
|
+ canvas.restore();
|
|
|
+ }
|
|
|
+
|
|
|
+ yStart -= verticalAxisValues.getDistanceBetweenMarkingsPx();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (verticalAxisRect.getDescription() != null) {
|
|
|
+ float textWidth = verticalAxisRect.getPaintDescription().measureText(verticalAxisRect.getDescription());
|
|
|
+ float x = -textWidth / 2;
|
|
|
+ float y = BarGraphView.this.getHeight() / 2 + verticalAxisRect.getDescriptionX();
|
|
|
+
|
|
|
+ canvas.save();
|
|
|
+ Rect rect = new Rect();
|
|
|
+ canvas.rotate(-90f, rect.exactCenterX(), BarGraphView.this.getHeight() / 2 + rect.exactCenterY());
|
|
|
+ canvas.drawText(verticalAxisRect.getDescription(), x, y + getPaddingLeft(), verticalAxisRect.getPaintDescription());
|
|
|
+ canvas.restore();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void drawValue(Canvas canvas, String valueDescription, float x1, float y1, float x2, float y2) {
|
|
|
+ canvas.drawRect(x1, y1, x2, y2, barConfig.getPaintRect());
|
|
|
+
|
|
|
+ // Value descriptor
|
|
|
+ if (valueDescription != null) {
|
|
|
+ float barWidth = x2 - x1;
|
|
|
+ if (barConfig.getPadding() + barConfig.getMaxTextHeight() <= barWidth) {
|
|
|
+ canvas.save();
|
|
|
+ Rect rect = new Rect();
|
|
|
+
|
|
|
+ canvas.rotate(-90.f, x1 + rect.exactCenterX(), y2 + rect.exactCenterY());
|
|
|
+ float x = x1 + getDimensionFromDp(VALUE_TEXT_OFFSET);
|
|
|
+ float y = y2 + barWidth - barConfig.getPadding();
|
|
|
+ canvas.drawText(valueDescription, x, y, barConfig.getPaintText());
|
|
|
+
|
|
|
+ canvas.restore();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void drawValues(Canvas canvas) {
|
|
|
+ float graphHeight = getGraphHeight();
|
|
|
+ float left = verticalAxisRect.getWidth();
|
|
|
+ float barWidth = barConfig.getBarWidth();
|
|
|
+ // float viewWidth = getMeasuredWidth();
|
|
|
+
|
|
|
+ for (int i = 0; i < adapter.getCount(); ++i) {
|
|
|
+ int barHeight = getBarHeight(adapter.getValue(i));
|
|
|
+
|
|
|
+ float x1 = left - offsetX + barConfig.getWhiteSpace() + getPaddingLeft();
|
|
|
+ float x2 = x1 + barConfig.getWhiteSpace() + barConfig.getBarWidth() - barConfig.getWhiteSpace();
|
|
|
+
|
|
|
+ left += barWidth + barConfig.getWhiteSpace();
|
|
|
+ // Check if it is too left, and goes into y axis
|
|
|
+ if (x1 + barWidth < verticalAxisRect.getWidth() + getPaddingLeft()) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if too right
|
|
|
+ if (x1 > getWidth() - getPaddingRight()) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Prevent overlap with Y-axis
|
|
|
+ if (x1 < verticalAxisRect.getWidth() + getPaddingLeft()) {
|
|
|
+ x1 = verticalAxisRect.getWidth() + getPaddingLeft();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (x2 > getWidth() - getPaddingRight()) {
|
|
|
+ x2 = getWidth() - getPaddingRight();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (x2 >= verticalAxisRect.getWidth()) {
|
|
|
+ float y1 = getPaddingTop() + graphHeight - barHeight;
|
|
|
+ float y2 = horizontalAxisRect.getAxisBottom();
|
|
|
+
|
|
|
+ drawValue(canvas, adapter.getValueDescription(i), x1, y1, x2, y2);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private float getDimensionFromDp(float dp) {
|
|
|
+ return dm.density * dp;
|
|
|
+ }
|
|
|
+
|
|
|
+ private float getDimensionFromSp(float sp) {
|
|
|
+ return dm.scaledDensity * sp;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int getBarHeight(double d) {
|
|
|
+ int res = (int) (d / verticalAxisValues.getTopMarkingHeight() * getGraphHeight());
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int getGraphHeight() {
|
|
|
+ int height = getHeight();
|
|
|
+ // Consider margins
|
|
|
+ return (int) (height - getPaddingTop() - horizontalAxisRect.getHeight() - getPaddingBottom());
|
|
|
+ }
|
|
|
+
|
|
|
+ private float getTotalWidth() {
|
|
|
+ if (adapter == null || adapter.getCount() == 0)
|
|
|
+ return 0;
|
|
|
+ return adapter.getCount() * barConfig.getBarWidth() + (adapter.getCount() - 1) * barConfig.getWhiteSpace();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Calculates available width where bars can be stored.
|
|
|
+ *
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private float getAvailableDataWidth() {
|
|
|
+ return (getWidth() - verticalAxisRect.getWidth() - getPaddingLeft() - getPaddingRight());
|
|
|
+ }
|
|
|
+
|
|
|
+ GestureDetector gd = new GestureDetector(this.getContext(), new OnGestureListener() {
|
|
|
+ @Override
|
|
|
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
|
|
+ scrollBy((int) distanceX, (int) distanceY);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean onDown(MotionEvent e) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onLongPress(MotionEvent e) {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onShowPress(MotionEvent e) {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean onSingleTapUp(MotionEvent e) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ private double maxDataValue;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean onTouchEvent(MotionEvent event) {
|
|
|
+ return gd.onTouchEvent(event);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void scrollBy(int dx, int dy) {
|
|
|
+ float candidateOffsetX = offsetX;
|
|
|
+ candidateOffsetX += dx;
|
|
|
+
|
|
|
+ // Maybe too far left?
|
|
|
+ if (candidateOffsetX < 0)
|
|
|
+ candidateOffsetX = 0;
|
|
|
+
|
|
|
+ float totalDataWidth = getTotalWidth();
|
|
|
+ float visibleDataWidth = getAvailableDataWidth();
|
|
|
+
|
|
|
+ float maxOffset = totalDataWidth - visibleDataWidth + getDimensionFromDp(1.0f);
|
|
|
+ if (maxOffset < 0)
|
|
|
+ maxOffset = 0;
|
|
|
+
|
|
|
+ // To far right?
|
|
|
+ if (candidateOffsetX > maxOffset) {
|
|
|
+ candidateOffsetX = maxOffset;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (candidateOffsetX != offsetX) {
|
|
|
+ offsetX = candidateOffsetX;
|
|
|
+ invalidate();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void adapterChanged() {
|
|
|
+ verticalAxisRect.init();
|
|
|
+ horizontalAxisRect.init();
|
|
|
+ barConfig.init();
|
|
|
+
|
|
|
+ // max vrijednost unutar Data arraya, max vrijednost podataka koje imamo
|
|
|
+ maxDataValue = getDataMaxValue();
|
|
|
+ // max vrijednost grafa, ovisi o max vrijednosti data arraya, ali nije
|
|
|
+ // ista (nekad imamo lufta)
|
|
|
+ maxGraphValue = getGraphMaxValue(maxDataValue);
|
|
|
+
|
|
|
+ verticalAxisValues.setHeight(-1);
|
|
|
+ verticalAxisValues.setAverageValue(getAverageDataValue());
|
|
|
+
|
|
|
+ invalidate();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setAdapter(BarGraphAdapter adapter) {
|
|
|
+ this.adapter = adapter;
|
|
|
+
|
|
|
+ adapterChanged();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void notifyDatasetChanged() {
|
|
|
+ adapterChanged();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ *
|
|
|
+ * Value returned by this function is >= maxDataValue.
|
|
|
+ * Basically, calculates number of digits in max value and rounds to the next value in that
|
|
|
+ * order of magnitude.
|
|
|
+ *
|
|
|
+ * Example:
|
|
|
+ * maxDataValue maxGraphValue
|
|
|
+ * 23.21 30.0
|
|
|
+ * 542.32 600.0
|
|
|
+ *
|
|
|
+ * @param maxDataValue
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private double getGraphMaxValue(double maxDataValue) {
|
|
|
+ // odmah računamo sve potrebno, da se ne zamaramo s tim na onDraw
|
|
|
+ // vrijednost za graf
|
|
|
+ // zanima me kolko dekadskih znamenki ima najveća vrijednost
|
|
|
+ int numberOfDigits = (int) Math.floor(Math.log10(maxDataValue)) + 1;
|
|
|
+ // osnova je 10^(broj_znakova - 1)
|
|
|
+ int order = (int) Math.pow(10, numberOfDigits - 1);
|
|
|
+ // pocetak je na n*order tako da je n*order najmanji veći za neki cijeli
|
|
|
+ // broj n
|
|
|
+ // ovaj broj nam je u biti logički maksimum
|
|
|
+ return ((int) (Math.ceil(maxDataValue / order))) * order;
|
|
|
+ }
|
|
|
+
|
|
|
+ private double getAverageDataValue() {
|
|
|
+ double res = getDataValuesSum();
|
|
|
+ return res / adapter.getCount();
|
|
|
+ }
|
|
|
+
|
|
|
+ private double getDataValuesSum() {
|
|
|
+ if (adapter == null || adapter.getCount() == 0)
|
|
|
+ return Double.NEGATIVE_INFINITY;
|
|
|
+
|
|
|
+ double res = 0.0;
|
|
|
+ for (int i = 0; i < adapter.getCount(); ++i)
|
|
|
+ res += adapter.getValue(i);
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+
|
|
|
+ private class VerticalAxisValues {
|
|
|
+ private int height;
|
|
|
+ private float minDistanceBetweenMarkings;
|
|
|
+ private float distanceBetweenMarkingsPx;
|
|
|
+ private double yieldBetweenMarkings;
|
|
|
+ private int markingsCount;
|
|
|
+ private double topMarkingHeight;
|
|
|
+ private double averageValue;
|
|
|
+
|
|
|
+ public VerticalAxisValues() {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private void normalize(double maxDataValue) {
|
|
|
+ int markingsCount = (int) Math.ceil(maxDataValue / getYieldBetweenMarkings());
|
|
|
+ double maxMarkingHeight = markingsCount * getYieldBetweenMarkings();
|
|
|
+ setTopMarkingHeight(maxMarkingHeight);
|
|
|
+ setMarkingsCount(markingsCount);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void update(int height, double maxGraphValue) {
|
|
|
+ this.height = height;
|
|
|
+
|
|
|
+ int maxNumberOfMarkings = calculateMaximumNumberOfVerticalMarkings(minDistanceBetweenMarkings);
|
|
|
+ yieldBetweenMarkings = maxGraphValue / maxNumberOfMarkings;
|
|
|
+
|
|
|
+ normalize(maxDataValue);
|
|
|
+ distanceBetweenMarkingsPx = calculateLogicalYWidth(verticalAxisValues.getMarkingsCount());
|
|
|
+ }
|
|
|
+
|
|
|
+ private float calculateLogicalYWidth(int count) {
|
|
|
+ float normYResolution = (float) getGraphHeight() / count;
|
|
|
+ return normYResolution;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getHeight() {
|
|
|
+ return height;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setHeight(int height) {
|
|
|
+ this.height = height;
|
|
|
+ }
|
|
|
+
|
|
|
+ public float getDistanceBetweenMarkingsPx() {
|
|
|
+ return distanceBetweenMarkingsPx;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setDistanceBetweenMarkingsPx(float distanceBetweenMarkingsPx) {
|
|
|
+ this.distanceBetweenMarkingsPx = distanceBetweenMarkingsPx;
|
|
|
+ }
|
|
|
+
|
|
|
+ public float getMinDistanceBetweenMarkings() {
|
|
|
+ return minDistanceBetweenMarkings;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setMinDistanceBetweenMarkings(float minDistanceBetweenMarkings) {
|
|
|
+ this.minDistanceBetweenMarkings = minDistanceBetweenMarkings;
|
|
|
+ }
|
|
|
+
|
|
|
+ public double getYieldBetweenMarkings() {
|
|
|
+ return yieldBetweenMarkings;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setYieldBetweenMarkings(double yYield) {
|
|
|
+ this.yieldBetweenMarkings = yYield;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getMarkingsCount() {
|
|
|
+ return markingsCount;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setMarkingsCount(int markingsCount) {
|
|
|
+ this.markingsCount = markingsCount;
|
|
|
+ }
|
|
|
+
|
|
|
+ public double getTopMarkingHeight() {
|
|
|
+ return topMarkingHeight;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setTopMarkingHeight(double topMarkingHeight) {
|
|
|
+ this.topMarkingHeight = topMarkingHeight;
|
|
|
+ }
|
|
|
+
|
|
|
+ public double getAverageValue() {
|
|
|
+ return averageValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setAverageValue(double averageValue) {
|
|
|
+ this.averageValue = averageValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private VerticalAxisValues verticalAxisValues = new VerticalAxisValues();
|
|
|
+
|
|
|
+ private void initVerticalAxisValues() {
|
|
|
+ if (verticalAxisValues.getHeight() != getHeight()) {
|
|
|
+ verticalAxisValues.update(getHeight(), maxGraphValue);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private int calculateMaximumNumberOfVerticalMarkings(float distance) {
|
|
|
+ // na koliko dijelova možemo raspodijeliti
|
|
|
+ int discreteY = (int) (getGraphHeight() / distance);
|
|
|
+ // trazimo najmanju nizu potenciju od 2 koja bi odgovorala discreteY
|
|
|
+ int log2 = (int) Math.floor(Math.log(discreteY) / Math.log(2.));
|
|
|
+ int normDiscreteY = (int) Math.pow(2, log2);
|
|
|
+ return normDiscreteY;
|
|
|
+ }
|
|
|
+
|
|
|
+ private double getDataMaxValue() {
|
|
|
+ double res = Double.NEGATIVE_INFINITY;
|
|
|
+ if (adapter != null) {
|
|
|
+ for (int i = 0; i < adapter.getCount(); ++i) {
|
|
|
+ if (adapter.getValue(i) > res) {
|
|
|
+ res = adapter.getValue(i);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isDrawAverageLine() {
|
|
|
+ return drawAverageLine;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setDrawAverageLine(boolean drawAvgLine) {
|
|
|
+ this.drawAverageLine = drawAvgLine;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setDrawXValues(boolean draw) {
|
|
|
+ horizontalAxisRect.setShowValues(draw);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setDrawYValues(boolean draw) {
|
|
|
+ verticalAxisRect.setShowValues(draw);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setDescriptionY(String desc) {
|
|
|
+ verticalAxisRect.setDescription(desc);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setDescriptionX(String desc) {
|
|
|
+ horizontalAxisRect.setDescription(desc);
|
|
|
+ }
|
|
|
+
|
|
|
+ private class BarConfig {
|
|
|
+ private float padding;
|
|
|
+ private float whiteSpace;
|
|
|
+ private Paint paintRect = null;
|
|
|
+ private Paint paintText = null;
|
|
|
+ private float barWidth;
|
|
|
+
|
|
|
+ public void init() {
|
|
|
+ float fontSize = barFontSizePx;
|
|
|
+ paintRect = new Paint();
|
|
|
+ paintRect.setColor(Color.parseColor("#81E5F0"));
|
|
|
+ paintRect.setAntiAlias(true);
|
|
|
+
|
|
|
+ paintText = new Paint();
|
|
|
+ paintText.setColor(Color.parseColor("#000000"));
|
|
|
+ paintText.setAntiAlias(true);
|
|
|
+ paintText.setTextSize(fontSize);
|
|
|
+
|
|
|
+ padding = barPaddingPx;
|
|
|
+ whiteSpace = getDimensionFromDp(1.0f);
|
|
|
+
|
|
|
+ barWidth = 2 * padding + getMaxTextHeight();
|
|
|
+ }
|
|
|
+
|
|
|
+ public float getPadding() {
|
|
|
+ return padding;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getMaxTextHeight() {
|
|
|
+ Rect bounds = new Rect();
|
|
|
+ paintText.getTextBounds("M", 0, 1, bounds);
|
|
|
+ return bounds.height();
|
|
|
+ }
|
|
|
+
|
|
|
+ public Paint getPaintRect() {
|
|
|
+ return paintRect;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setPaintRect(Paint paintRect) {
|
|
|
+ this.paintRect = paintRect;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Paint getPaintText() {
|
|
|
+ return paintText;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setPaintText(Paint paintText) {
|
|
|
+ this.paintText = paintText;
|
|
|
+ }
|
|
|
+
|
|
|
+ public float getBarWidth() {
|
|
|
+ return barWidth;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setBarWidth(float barWidth) {
|
|
|
+ this.barWidth = barWidth;
|
|
|
+ }
|
|
|
+
|
|
|
+ public float getWhiteSpace() {
|
|
|
+ return whiteSpace;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setWhiteSpace(float whiteSpace) {
|
|
|
+ this.whiteSpace = whiteSpace;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private abstract class AxisRect {
|
|
|
+ float lineSpacing;
|
|
|
+ float fontSize;
|
|
|
+ float axisMarkingsLength;
|
|
|
+
|
|
|
+ Paint paintLine = null;
|
|
|
+ Paint paintDescription = null;
|
|
|
+
|
|
|
+ boolean showValues = true;
|
|
|
+ String description = null;
|
|
|
+
|
|
|
+ public void init() {
|
|
|
+ fontSize = axisValuesFontSizePx;
|
|
|
+ axisMarkingsLength = getDimensionFromDp(DEFAULT_MARKINGS_LENGTH_DP);
|
|
|
+
|
|
|
+ paintLine = new Paint();
|
|
|
+ paintLine.setAntiAlias(true);
|
|
|
+ paintLine.setColor(Color.BLACK);
|
|
|
+ paintLine.setTextSize(fontSize);
|
|
|
+
|
|
|
+ paintDescription = new Paint();
|
|
|
+ paintDescription.setAntiAlias(true);
|
|
|
+ paintDescription.setColor(Color.BLACK);
|
|
|
+ paintDescription.setTextSize(getDimensionFromSp(DEFAULT_AXIS_DESCRIPTION_FONT_SIZE_SP));
|
|
|
+
|
|
|
+ lineSpacing = getDimensionFromDp(DEFAULT_AXIS_LINE_SPACING_DP);
|
|
|
+ }
|
|
|
+
|
|
|
+ public Paint getPaintLine() {
|
|
|
+ return paintLine;
|
|
|
+ }
|
|
|
+
|
|
|
+ public float getFontSize() {
|
|
|
+ return fontSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setFontSize(float fontSize) {
|
|
|
+ this.fontSize = fontSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ public float getLineSpacing() {
|
|
|
+ return lineSpacing;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setLineSpacing(float lineSpacing) {
|
|
|
+ this.lineSpacing = lineSpacing;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isShowValues() {
|
|
|
+ return showValues;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setShowValues(boolean showValues) {
|
|
|
+ this.showValues = showValues;
|
|
|
+ }
|
|
|
+
|
|
|
+ public float getAxisMarkingsLength() {
|
|
|
+ return axisMarkingsLength;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getTextHeight(Paint paint, String s) {
|
|
|
+ Rect bounds = new Rect();
|
|
|
+ paint.getTextBounds(s, 0, s.length(), bounds);
|
|
|
+ return bounds.height();
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getTextHeight() {
|
|
|
+ Rect bounds = new Rect();
|
|
|
+ paintLine.getTextBounds("0", 0, 1, bounds);
|
|
|
+ return bounds.height();
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getDescription() {
|
|
|
+ return description;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setDescription(String description) {
|
|
|
+ this.description = description;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Paint getPaintDescription() {
|
|
|
+ return paintDescription;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setPaintDescription(Paint paintDescription) {
|
|
|
+ this.paintDescription = paintDescription;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private class VerticalAxisRect extends AxisRect {
|
|
|
+ @Override
|
|
|
+ public void init() {
|
|
|
+ super.init();
|
|
|
+ }
|
|
|
+
|
|
|
+ public float getWidth() {
|
|
|
+ float res = getAxisLeft();
|
|
|
+ res += paintLine.getStrokeWidth();
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+
|
|
|
+ public float getAxisLeft() {
|
|
|
+ float res = 0;
|
|
|
+ if (showValues) {
|
|
|
+ res += getTextHeight();
|
|
|
+ res += lineSpacing;
|
|
|
+ }
|
|
|
+ if (description != null) {
|
|
|
+ res += getTextHeight(paintDescription, description);
|
|
|
+ res += lineSpacing * 2;
|
|
|
+ }
|
|
|
+ res += axisMarkingsLength;
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+
|
|
|
+ public float getValuesX() {
|
|
|
+ float res = getTextHeight();
|
|
|
+ if (description != null) res += lineSpacing * 2 + getTextHeight(paintDescription, description);
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+
|
|
|
+ public float getDescriptionX() {
|
|
|
+ return getTextHeight(paintDescription, description);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private class HorizontalAxisRect extends AxisRect {
|
|
|
+ @Override
|
|
|
+ public void init() {
|
|
|
+ super.init();
|
|
|
+ }
|
|
|
+
|
|
|
+ public float getHeight() {
|
|
|
+ float res = 0;
|
|
|
+ if (showValues) {
|
|
|
+ res += getTextHeight();
|
|
|
+ res += lineSpacing;
|
|
|
+ }
|
|
|
+ if (description != null) {
|
|
|
+ res += getTextHeight(paintDescription, description);
|
|
|
+ res += lineSpacing * 2;
|
|
|
+ }
|
|
|
+ res += axisMarkingsLength;
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+
|
|
|
+ public float getMarkingValuesY() {
|
|
|
+ float res = BarGraphView.this.getHeight() - BarGraphView.this.getPaddingBottom();
|
|
|
+ if (description != null)
|
|
|
+ res -= lineSpacing * 2 + getTextHeight(paintDescription, description);
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+
|
|
|
+ public float getDescriptionY() {
|
|
|
+ return BarGraphView.this.getHeight() - BarGraphView.this.getPaddingBottom();
|
|
|
+ }
|
|
|
+
|
|
|
+ public float getAxisBottom() {
|
|
|
+ float viewHeight = BarGraphView.this.getHeight();
|
|
|
+ float axisHeight = getHeight();
|
|
|
+ return viewHeight - axisHeight - getPaddingBottom();
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+}
|