1ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey/*
2ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * Copyright (C) 2011 The Android Open Source Project
3ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey *
4ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * Licensed under the Apache License, Version 2.0 (the "License");
5ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * you may not use this file except in compliance with the License.
6ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * You may obtain a copy of the License at
7ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey *
8ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey *      http://www.apache.org/licenses/LICENSE-2.0
9ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey *
10ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * Unless required by applicable law or agreed to in writing, software
11ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * distributed under the License is distributed on an "AS IS" BASIS,
12ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * See the License for the specific language governing permissions and
14ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * limitations under the License.
15ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey */
16ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
17ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkeypackage com.android.settings.widget;
18ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
19ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkeyimport android.content.Context;
2054d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkeyimport android.content.res.TypedArray;
21ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkeyimport android.graphics.Rect;
2252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkeyimport android.util.AttributeSet;
23ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkeyimport android.view.Gravity;
24ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkeyimport android.view.View;
2554d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkeyimport android.view.ViewDebug;
26ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkeyimport android.widget.FrameLayout;
27ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
28e6c5003278184c202833209164ddf1ae8c083f12Jeff Sharkeyimport com.android.internal.util.Preconditions;
2954d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkeyimport com.android.settings.R;
3054d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey
31ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey/**
32ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * Container for two-dimensional chart, drawn with a combination of
33ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * {@link ChartGridView}, {@link ChartNetworkSeriesView} and {@link ChartSweepView}
34ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * children. The entire chart uses {@link ChartAxis} to map between raw values
35ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * and screen coordinates.
36ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey */
37ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkeypublic class ChartView extends FrameLayout {
38ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    // TODO: extend something that supports two-dimensional scrolling
39ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
4079d8e80a304922c34a6bd344f1fa49dd7dfd106dFabrice Di Meglio    private static final int SWEEP_GRAVITY = Gravity.TOP | Gravity.START;
41f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey
4252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    ChartAxis mHoriz;
4352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    ChartAxis mVert;
44ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
4554d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey    @ViewDebug.ExportedProperty
4654d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey    private int mOptimalWidth = -1;
4754d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey    private float mOptimalWidthWeight = 0;
4854d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey
49ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    private Rect mContent = new Rect();
50ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
5152c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public ChartView(Context context) {
5252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        this(context, null, 0);
5352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
54ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
5552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public ChartView(Context context, AttributeSet attrs) {
5652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        this(context, attrs, 0);
5752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
5852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
5952c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public ChartView(Context context, AttributeSet attrs, int defStyle) {
6052c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        super(context, attrs, defStyle);
61ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
6254d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey        final TypedArray a = context.obtainStyledAttributes(
6354d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey                attrs, R.styleable.ChartView, defStyle, 0);
6454d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey        setOptimalWidth(a.getDimensionPixelSize(R.styleable.ChartView_optimalWidth, -1),
6554d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey                a.getFloat(R.styleable.ChartView_optimalWidthWeight, 0));
6654d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey        a.recycle();
6754d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey
68ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        setClipToPadding(false);
69ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        setClipChildren(false);
70ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
71ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
7252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    void init(ChartAxis horiz, ChartAxis vert) {
73e6c5003278184c202833209164ddf1ae8c083f12Jeff Sharkey        mHoriz = Preconditions.checkNotNull(horiz, "missing horiz");
74e6c5003278184c202833209164ddf1ae8c083f12Jeff Sharkey        mVert = Preconditions.checkNotNull(vert, "missing vert");
7552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
7652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
7754d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey    public void setOptimalWidth(int optimalWidth, float optimalWidthWeight) {
7854d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey        mOptimalWidth = optimalWidth;
7954d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey        mOptimalWidthWeight = optimalWidthWeight;
8054d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey        requestLayout();
8154d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey    }
8254d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey
8354d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey    @Override
8454d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
8554d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
8654d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey
8754d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey        final int slack = getMeasuredWidth() - mOptimalWidth;
8854d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey        if (mOptimalWidth > 0 && slack > 0) {
8954d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey            final int targetWidth = (int) (mOptimalWidth + (slack * mOptimalWidthWeight));
9054d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey            widthMeasureSpec = MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY);
9154d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
9254d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey        }
9354d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey    }
9454d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey
95ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    @Override
96ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    protected void onLayout(boolean changed, int l, int t, int r, int b) {
978a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey        mContent.set(getPaddingLeft(), getPaddingTop(), r - l - getPaddingRight(),
988a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey                b - t - getPaddingBottom());
99ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        final int width = mContent.width();
100ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        final int height = mContent.height();
101ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
102ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        // no scrolling yet, so tell dimensions to fill exactly
103ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        mHoriz.setSize(width);
104ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        mVert.setSize(height);
105ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
106ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        final Rect parentRect = new Rect();
107ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        final Rect childRect = new Rect();
108ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
109ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        for (int i = 0; i < getChildCount(); i++) {
110ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            final View child = getChildAt(i);
111ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            final LayoutParams params = (LayoutParams) child.getLayoutParams();
112ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
113ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            parentRect.set(mContent);
114ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
115b654846300f79e9e4c605ce62d09f9a05d232feeJeff Sharkey            if (child instanceof ChartNetworkSeriesView) {
116ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey                // series are always laid out to fill entire graph area
117ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey                // TODO: handle scrolling for series larger than content area
118ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey                Gravity.apply(params.gravity, width, height, parentRect, childRect);
119ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey                child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
120ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
121b654846300f79e9e4c605ce62d09f9a05d232feeJeff Sharkey            } else if (child instanceof ChartGridView) {
122b654846300f79e9e4c605ce62d09f9a05d232feeJeff Sharkey                // Grid uses some extra room for labels
123b654846300f79e9e4c605ce62d09f9a05d232feeJeff Sharkey                Gravity.apply(params.gravity, width, height, parentRect, childRect);
124b654846300f79e9e4c605ce62d09f9a05d232feeJeff Sharkey                child.layout(childRect.left, childRect.top, childRect.right,
125b654846300f79e9e4c605ce62d09f9a05d232feeJeff Sharkey                        childRect.bottom + child.getPaddingBottom());
126b654846300f79e9e4c605ce62d09f9a05d232feeJeff Sharkey
127ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            } else if (child instanceof ChartSweepView) {
12855d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey                layoutSweep((ChartSweepView) child, parentRect, childRect);
12955d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey                child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
130ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            }
13155d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        }
13255d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    }
13355d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
13455d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    protected void layoutSweep(ChartSweepView sweep) {
13555d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        final Rect parentRect = new Rect(mContent);
13655d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        final Rect childRect = new Rect();
13755d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
13855d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        layoutSweep(sweep, parentRect, childRect);
13955d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        sweep.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
14055d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    }
141ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
14255d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    protected void layoutSweep(ChartSweepView sweep, Rect parentRect, Rect childRect) {
14355d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        final Rect sweepMargins = sweep.getMargins();
14455d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
14555d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        // sweep is always placed along specific dimension
14655d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        if (sweep.getFollowAxis() == ChartSweepView.VERTICAL) {
14755d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            parentRect.top += sweepMargins.top + (int) sweep.getPoint();
14855d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            parentRect.bottom = parentRect.top;
14955d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            parentRect.left += sweepMargins.left;
15055d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            parentRect.right += sweepMargins.right;
15155d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            Gravity.apply(SWEEP_GRAVITY, parentRect.width(), sweep.getMeasuredHeight(),
15255d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey                    parentRect, childRect);
15355d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
15455d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        } else {
15555d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            parentRect.left += sweepMargins.left + (int) sweep.getPoint();
15655d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            parentRect.right = parentRect.left;
15755d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            parentRect.top += sweepMargins.top;
15855d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            parentRect.bottom += sweepMargins.bottom;
15955d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            Gravity.apply(SWEEP_GRAVITY, sweep.getMeasuredWidth(), parentRect.height(),
16055d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey                    parentRect, childRect);
161ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        }
162ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
163ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey}
164