/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.redgeek.android.eventrend.calendar; import java.util.ArrayList; import java.util.Calendar; import net.redgeek.android.eventrend.Preferences; import net.redgeek.android.eventrend.calendar.CalendarPlot.PaintIndex; import net.redgeek.android.eventrend.primitives.Datapoint; import net.redgeek.android.eventrend.primitives.TimeSeries; import net.redgeek.android.eventrend.primitives.TimeSeriesCollector; import net.redgeek.android.eventrend.primitives.Tuple; import net.redgeek.android.eventrend.util.DateUtil; import net.redgeek.android.eventrend.util.Number; import net.redgeek.android.eventrend.util.DateUtil.Period; import net.redgeek.android.eventrend.util.Number.TrendState; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; public class CalendarPlotYear { private static final int YEAR_PADDING = 5; // UI elements private ArrayList<Paint> mPaints; // Private data private Context mCtx; private TimeSeriesCollector mTSC; private DateUtil mDates; private float mSensitivity; private float mCellWidth; private float mCellHeight; private float mColorHeight; private int mNYears = 5; private Tuple mDimensions; private long mStartMS; private long mFocusStart; private long mFocusEnd; public CalendarPlotYear(Context context, TimeSeriesCollector tsc, ArrayList<Paint> paints, Tuple dimensions) { mTSC = tsc; mCtx = context; mPaints = paints; setupData(dimensions); } private void setupData(Tuple dimensions) { mDimensions = new Tuple(); setDimensions(dimensions); mDates = new DateUtil(); mSensitivity = Preferences.getStdDevSensitivity(mCtx); setCellSizes(); } public void setDimensions(Tuple dimensions) { mDimensions.set(dimensions); if (mDimensions.y > mDimensions.x) mNYears = 5; else mNYears = 3; setCellSizes(); } public void setStart(long startMs) { mStartMS = startMs; } private void setCellSizes() { mCellHeight = (mDimensions.y - (CalendarView.TEXT_HEIGHT + YEAR_PADDING) - (YEAR_PADDING * (mNYears + 1)) - (CalendarView.TEXT_HEIGHT * (mNYears + 1))) / mNYears; mCellWidth = (mDimensions.x) / 12.0f; mColorHeight = mCellHeight / mTSC.numSeries(); } public synchronized void plot(Canvas canvas) { if (canvas == null) return; if (mDimensions.x <= 0 || mDimensions.y <= 0) return; drawYear(canvas); return; } private int positionToRow(int position) { return position / 12; } private int positionToColumn(int position) { return position % 12; } private Tuple getCellTopLeft(int position) { Tuple t = new Tuple(); int row = positionToRow(position); t.x = (positionToColumn(position) * mCellWidth); t.y = (CalendarView.TEXT_HEIGHT * 2) + YEAR_PADDING; t.y += (row * mCellHeight); t.y += row * (YEAR_PADDING + CalendarView.TEXT_HEIGHT + YEAR_PADDING); return t; } private Tuple getCellBottomRight(int position) { Tuple t = new Tuple(); int row = positionToRow(position); t.x = (positionToColumn(position) * mCellWidth) + mCellWidth; t.y = (CalendarView.TEXT_HEIGHT * 2) + YEAR_PADDING; t.y += (row * mCellHeight) + mCellHeight; t.y += row * (YEAR_PADDING + CalendarView.TEXT_HEIGHT + YEAR_PADDING); return t; } private Paint mapTrendStateToPaint(TrendState state) { if (state == TrendState.UP_45_GOOD || state == TrendState.DOWN_45_GOOD) return mPaints.get(PaintIndex.DATUM_GOOD4.ordinal()); else if (state == TrendState.UP_30_GOOD || state == TrendState.DOWN_30_GOOD) return mPaints.get(PaintIndex.DATUM_GOOD3.ordinal()); else if (state == TrendState.UP_15_GOOD || state == TrendState.DOWN_15_GOOD) return mPaints.get(PaintIndex.DATUM_GOOD2.ordinal()); else if (state == TrendState.UP_45_BAD || state == TrendState.DOWN_45_BAD) return mPaints.get(PaintIndex.DATUM_BAD4.ordinal()); else if (state == TrendState.UP_30_BAD || state == TrendState.DOWN_30_BAD) return mPaints.get(PaintIndex.DATUM_BAD3.ordinal()); else if (state == TrendState.UP_15_BAD || state == TrendState.DOWN_15_BAD) return mPaints.get(PaintIndex.DATUM_BAD2.ordinal()); else if (state == TrendState.UP_15 || state == TrendState.DOWN_15 || state == TrendState.FLAT) return mPaints.get(PaintIndex.DATUM_EVEN.ordinal()); else if (state == TrendState.FLAT_GOAL) return mPaints.get(PaintIndex.DATUM_EVEN_GOAL.ordinal()); return null; } private int setStartTime() { int focusYear; Calendar cal = mDates.getCalendar(); cal.setTimeInMillis(mStartMS); DateUtil.setToPeriodStart(cal, Period.YEAR); focusYear = mDates.get(Calendar.YEAR); long ms = cal.getTimeInMillis(); mDates.setBaseTime(ms); mDates.advance(Period.YEAR, -2); return focusYear; } private void drawYearMonthBackground(Canvas canvas, boolean focused, int position, int month, float lastTrend, float thisTrend, float goal, float stdDev) { Paint p = null; Tuple topLeft = getCellTopLeft(position); Tuple bottomRight = getCellBottomRight(position); // RectF cell = new RectF(topLeft.x, topLeft.y, bottomRight.x, // bottomRight.y); RectF cell; if (mNYears == 5) { cell = new RectF(topLeft.x, topLeft.y, bottomRight.x, topLeft.y + ((bottomRight.y - topLeft.y) / 3.0f)); } else { cell = new RectF(topLeft.x, topLeft.y, bottomRight.x, topLeft.y + ((bottomRight.y - topLeft.y) / 2.0f)); } float unit = stdDev * mSensitivity; float half = unit / 2; float quarter = half / 2; float delta = thisTrend - lastTrend; float absDelta = Math.abs(delta); if (absDelta > 0 && absDelta > quarter) { if ((delta > 0 && goal > thisTrend) || (delta < 0 && goal < thisTrend)) { if (delta > unit) p = mPaints.get(PaintIndex.DATUM_GOOD4.ordinal()); else if (delta > half + quarter) p = mPaints.get(PaintIndex.DATUM_GOOD3.ordinal()); else if (delta > half) p = mPaints.get(PaintIndex.DATUM_GOOD2.ordinal()); else // if (delta > quarter) p = mPaints.get(PaintIndex.DATUM_GOOD1.ordinal()); } else { if (delta > unit) p = mPaints.get(PaintIndex.DATUM_BAD4.ordinal()); else if (delta > half + quarter) p = mPaints.get(PaintIndex.DATUM_BAD3.ordinal()); else if (delta > half) p = mPaints.get(PaintIndex.DATUM_BAD2.ordinal()); else // if (delta > quarter) p = mPaints.get(PaintIndex.DATUM_BAD1.ordinal()); } } else { // even if (Math.abs(thisTrend - goal) < half) p = mPaints.get(PaintIndex.DATUM_EVEN_GOAL.ordinal()); else p = null; // p = mPaints.get(PaintIndex.DATUM_EVEN.ordinal()); } if (p != null) { canvas.drawRect(cell, p); } } private void drawYearMonthValue(Canvas canvas, int position, float prevValue, float thisValue, float goal, float stdDev) { Paint p; Tuple topLeft = getCellTopLeft(position); Tuple bottomRight = getCellBottomRight(position); float unit = stdDev * mSensitivity; float half = unit / 2; float quarter = half / 2; float delta = thisValue - prevValue; float absDelta = Math.abs(delta); if (absDelta > 0 && absDelta > quarter) { if ((delta > 0 && goal > thisValue) || (delta < 0 && goal < thisValue)) { if (delta > unit) p = mPaints.get(PaintIndex.DATUM_GOOD4.ordinal()); else if (delta > half + quarter) p = mPaints.get(PaintIndex.DATUM_GOOD3.ordinal()); else if (delta > half) p = mPaints.get(PaintIndex.DATUM_GOOD2.ordinal()); else // if (delta > quarter) p = mPaints.get(PaintIndex.DATUM_GOOD1.ordinal()); } else { if (delta > unit) p = mPaints.get(PaintIndex.DATUM_BAD4.ordinal()); else if (delta > half + quarter) p = mPaints.get(PaintIndex.DATUM_BAD3.ordinal()); else if (delta > half) p = mPaints.get(PaintIndex.DATUM_BAD2.ordinal()); else // if (delta > quarter) p = mPaints.get(PaintIndex.DATUM_BAD1.ordinal()); } } else { // even if (Math.abs(thisValue - goal) < half) p = mPaints.get(PaintIndex.DATUM_EVEN_GOAL.ordinal()); else p = mPaints.get(PaintIndex.DATUM_EVEN.ordinal()); } int offset = 0; if (mNYears == 5) { if (position % 2 == 0) offset = -CalendarView.TEXT_HEIGHT - 3; } // Paint p = mPaints.get(PaintIndex.VALUE.ordinal()); canvas.drawText("" + Number.Round(thisValue), topLeft.x + 3, bottomRight.y + offset - 4, p); } private void drawYearMonthBorder(Canvas canvas, boolean focused, int position, int month) { Tuple topLeft = getCellTopLeft(position); Tuple bottomRight = getCellBottomRight(position); Paint p; if (focused == true) { p = mPaints.get(PaintIndex.BORDER_SECONDARY.ordinal()); RectF cell = new RectF(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y); canvas.drawRect(cell, p); } else { p = mPaints.get(PaintIndex.BORDER_SECONDARY.ordinal()); RectF cell = new RectF(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y); canvas.drawRect(cell, p); } } private void drawFocusBorder(Canvas canvas, int firstPosition, int lastPosition) { Tuple start = new Tuple(); Tuple topLeft = getCellTopLeft(firstPosition); Tuple bottomRight = getCellBottomRight(lastPosition); RectF cell = new RectF(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y); Paint p = mPaints.get(PaintIndex.BORDER_PRIMARY.ordinal()); canvas.drawRect(cell, p); p = mPaints.get(PaintIndex.BORDER_HIGHLIGHT.ordinal()); canvas.drawRect(cell, p); } private void drawMonthHeader(Canvas canvas) { Paint p = mPaints.get(PaintIndex.LABEL.ordinal()); for (int i = 0; i < 12; i++) { Tuple topLeft = getCellTopLeft(i); canvas.drawText(DateUtil.MONTHS[i], topLeft.x + 3, CalendarView.TEXT_HEIGHT - 3, p); } } private void drawYearHeader(Canvas canvas, int position, int year) { Paint p = mPaints.get(PaintIndex.LABEL.ordinal()); Tuple topLeft = getCellTopLeft(position); Tuple bottomRight = getCellBottomRight(position + 11); canvas.drawText("" + year, topLeft.x + YEAR_PADDING + 3, topLeft.y - 3, p); } private void drawYear(Canvas canvas) { Calendar tmp = Calendar.getInstance(); TimeSeries ts; Datapoint prev, current; int focusYear; int month, year; int firstPosition = 0; int lastPosition = 0; focusYear = setStartTime(); Calendar cal = mDates.getCalendar(); long ms = cal.getTimeInMillis(); drawMonthHeader(canvas); int position = 0; for (int i = 0; i < mNYears * 12; i++, position++) { year = mDates.get(Calendar.YEAR); month = mDates.get(Calendar.MONTH); ms = mDates.getCalendar().getTimeInMillis(); for (int s = 0; s < mTSC.numSeries(); s++) { ts = (TimeSeries) mTSC.getSeries(s); if (ts == null || mTSC.isSeriesEnabled(ts.getDbRow().getId()) == false) continue; current = ts.findPostNeighbor(ms); prev = ts.findPreNeighbor(ms - 1); if (current != null && prev != null) { if (focusYear == mDates.get(Calendar.YEAR)) { drawYearMonthBackground(canvas, true, position, month, prev.mTrend.y, current.mTrend.y, ts.getDbRow().getGoal(), current.mStdDev); } else { drawYearMonthBackground(canvas, false, position, month, prev.mTrend.y, current.mTrend.y, ts.getDbRow().getGoal(), current.mStdDev); } } if (current != null) { float oldVal = current.mValue.y; if (prev != null) oldVal = prev.mValue.y; tmp.setTimeInMillis(current.mMillis); if (month == tmp.get(Calendar.MONTH) && year == tmp.get(Calendar.YEAR)) { drawYearMonthValue(canvas, position, oldVal, current.mValue.y, ts .getDbRow().getGoal(), current.mStdDev); } } } if (month == 0) { drawYearHeader(canvas, position, year); } if (focusYear == mDates.get(Calendar.YEAR)) { if (month == 0) { firstPosition = position; mFocusStart = ms; } if (month == 11) { lastPosition = position; mFocusEnd = ms + DateUtil.YEAR_MS - 1; } drawYearMonthBorder(canvas, true, position, month); } else { drawYearMonthBorder(canvas, false, position, month); } mDates.advance(Period.MONTH, 1); } drawFocusBorder(canvas, firstPosition, lastPosition); } public long getFocusStart() { return mFocusStart; } public long getFocusEnd() { return mFocusEnd; } }