/* * 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.graph; import java.util.Calendar; import net.redgeek.android.eventrend.Preferences; import net.redgeek.android.eventrend.backgroundtasks.DataCollectionTask; 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.GUITask; import net.redgeek.android.eventrend.util.GUITaskQueue; import net.redgeek.android.eventrend.util.ProgressIndicator; import net.redgeek.android.eventrend.util.DateUtil.Period; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.os.Handler; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.View.OnLongClickListener; import android.widget.TextView; import android.widget.ZoomControls; /** * By far the most convoluted and dis-organized of classes, Currently undergoing * refactoring to minimize the noodles. * * @author barclay * */ public class GraphView extends View implements OnLongClickListener, GUITask { // Various UI parameters public static final int TOP_MARGIN = 5; public static final int LEFT_MARGIN = 40; public static final int RIGHT_MARGIN = 10; public static final int BOTTOM_MARGIN = 40; public static final int X_TICK = 3; public static final int Y_TICK = 3; public static final float POINT_RADIUS = 2.0f; public static final int PATH_WIDTH = 2; public static final int TREND_WIDTH = 1; public static final int GOAL_WIDTH = 1; public static final int AXIS_WIDTH = 1; public static final int LABEL_WIDTH = 1; public static final int TREND_DASH_WIDTH = 4; public static final int GOAL_DASH_WIDTH = 2; public static final int TICK_LENGTH = 5; public static final float TICK_MIN_DISTANCE = 25.0f; public static final float ZOOM_FACTOR = 1.1f; public static final int TEXT_HEIGHT = 15; public static final int PLOT_TOP_PAD = 10; public static final int PLOT_BOTTOM_PAD = 15; public static final float POINT_TOUCH_RADIUS = 20.0f; public static final int ZOOM_CTRL_HIDE_MS = 3000; public static final int ZOOM_CTRL_LINES_MS = 200; public static final float MINIMUM_DELTA = 0.00001f; // UI elements private ZoomControls mZoomControls; private Graph mGraph; private ProgressIndicator.Titlebar mProgress; private TextView mStatus; private Canvas mCanvas; // Listeners private View.OnClickListener mZoomInListener; private View.OnClickListener mZoomOutListener; // Private data private Context mCtx; private TimeSeriesCollector mTSC; private Calendar mCal; private Tuple mLastEvent; // Tasks and handlers private Handler mZoomHandler; private Runnable mHideZoomControls; private DataCollectionTask mCollector; public GraphView(Context context, ZoomControls zoomControls, TimeSeriesCollector tsc) { super(context); mCtx = context; mTSC = tsc; setupData(); setupUI(zoomControls); } private void setupData() { mCal = Calendar.getInstance(); mCollector = new DataCollectionTask(mTSC); mGraph = new Graph(mCtx, mTSC, getWidth(), getHeight()); mZoomHandler = new Handler(); } private void setupUI(ZoomControls zoomControls) { setupListeners(); mStatus = ((GraphActivity) mCtx).getGraphStatusTextView(); setColorScheme(); setFocusableInTouchMode(true); setOnLongClickListener(this); mZoomControls = zoomControls; mZoomControls.setOnZoomInClickListener(mZoomInListener); mZoomControls.setOnZoomOutClickListener(mZoomOutListener); mProgress = new ProgressIndicator.Titlebar(mCtx); mHideZoomControls = new Runnable() { public void run() { mZoomControls.setVisibility(View.INVISIBLE); } }; } private void setupListeners() { mZoomInListener = new View.OnClickListener() { public void onClick(View v) { scheduleHideZoomControls(); long start = mGraph.getGraphStart(); long end = mGraph.getGraphEnd(); long delta = (end - start) / 4; start += delta; end -= delta; if (start + DateUtil.MINUTE_MS * 10 >= end) { end += ((DateUtil.MINUTE_MS * 10) / 2); start = end - ((DateUtil.MINUTE_MS * 10) / 2); } mGraph.setGraphRange(start, end); updateData(); } }; mZoomOutListener = new View.OnClickListener() { public void onClick(View v) { scheduleHideZoomControls(); long start = mGraph.getGraphStart(); long end = mGraph.getGraphEnd(); long delta = (end - start) / 2; start -= delta; end += delta; if (start < 0) start = 0; mGraph.setGraphRange(start, end); updateData(); } }; } public void setColorScheme() { if (Preferences.getDefaultGraphIsBlack(mCtx) == true) setBackgroundColor(Color.BLACK); else setBackgroundColor(Color.WHITE); mGraph.setColorScheme(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { mGraph.setGraphSize(w, h); } @Override public boolean onTouchEvent(MotionEvent event) { scheduleHideZoomControls(); if (event.getAction() == MotionEvent.ACTION_DOWN) { requestFocus(); mLastEvent = new Tuple(event.getX(), event.getY()); Tuple plotCoords = new Tuple(mLastEvent); plotCoords.minus(mGraph.getPlotOffset()); lookupDatapoint(plotCoords); return super.onTouchEvent(event); } if (event.getAction() != MotionEvent.ACTION_MOVE) { mLastEvent = null; return super.onTouchEvent(event); } Tuple movement = new Tuple(event.getX(), event.getY()); if (mLastEvent != null) { movement = movement.minus(mLastEvent); } else if (event.getHistorySize() > 0) { movement = movement.minus(new Tuple(event.getHistoricalX(0), event .getHistoricalY(0))); } else { mLastEvent = movement; return super.onTouchEvent(event); } mLastEvent = null; long start = mGraph.getGraphStart(); long end = mGraph.getGraphEnd(); movement.x *= ((end - start) / getWidth()); movement.x *= -1; movement.y = 0; end += movement.x; if (end < 0) end = 0; start += movement.x; if (start < 0) start = 0; mGraph.setGraphRange(start, end); updateData(); return super.onTouchEvent(event); } private void scheduleHideZoomControls() { mZoomHandler.removeCallbacks(mHideZoomControls); mZoomHandler.postDelayed(mHideZoomControls, ZOOM_CTRL_HIDE_MS); } public boolean onLongClick(View v) { scheduleHideZoomControls(); mZoomControls.setVisibility(View.VISIBLE); return true; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (getWidth() > 0 && getHeight() > 0) { mCanvas = canvas; mGraph.plot(mCanvas); } } public void executeNonGuiTask() throws Exception { long start = mGraph.getGraphStart(); long end = mGraph.getGraphEnd(); mCollector.setSpan(start, end); mCollector.doCollection(); } public void afterExecute() { updateStatus(); setFocusable(true); invalidate(); } public void onFailure(Throwable t) { } public void updateData() { long start = mGraph.getGraphStart(); long end = mGraph.getGraphEnd(); mGraph.setGraphRange(start, end); // Useful debugging: uncomment the following, and comment out the // addTask() below -- this makes the data collection run synchronously. // mCollector.setSpan(start, end); // mCollector.doCollection(); // updateStatus(); // invalidate(); GUITaskQueue.getInstance().addTask(mProgress, this); } private void updateStatus() { long start = mGraph.getGraphStart(); long end = mGraph.getGraphEnd(); String str = new String(); if (mTSC.getAutoAggregation() == true) { Period span = mGraph.getSpan(); str = "Aggregation: " + DateUtil.mapPeriodToString(span) + " "; } if (mGraph.mSelectedDatapoint != null) { str += mGraph.mSelectedDatapoint.toLabelString() + ": " + mGraph.mSelectedDatapoint.mValue.y; mStatus.setText(str); mStatus.setTextColor(mGraph.mSelectedColor); return; } if (mTSC.getAutoAggregation() == false) { str += DateUtil.toTimestamp(start) + " - " + DateUtil.toTimestamp(end); } mStatus.setText(str); mStatus.setTextColor(Color.GRAY); } public void snapToSpan() { long start = mGraph.getGraphStart(); long end = mGraph.getGraphEnd(); long delta = end - start; Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(start); DateUtil.setToPeriodStart(cal, mGraph.getSpan()); start = cal.getTimeInMillis(); end = start + delta; mGraph.setGraphRange(start, end); Log.v("graphview", "snapToSpan() -> updateData"); updateData(); } public void resetZoom() { long start = mGraph.getGraphStart(); long end = mGraph.getGraphEnd(); end = mCal.getTimeInMillis(); start = end - DateUtil.DAY_MS * 7; mGraph.setGraphRange(start, end); } public void setTrendView(boolean b) { mGraph.viewTrends(b); } public void setGoalView(boolean b) { mGraph.viewGoals(b); } public void setMarkersView(boolean b) { mGraph.viewMarkers(b); } public void lookupDatapoint(Tuple t) { mGraph.lookupDatapoint(t); updateStatus(); } public Graph getGraph() { return mGraph; } }