1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings.widget;
18
19import static com.google.common.base.Preconditions.checkNotNull;
20
21import android.content.Context;
22import android.content.res.TypedArray;
23import android.graphics.Rect;
24import android.util.AttributeSet;
25import android.view.Gravity;
26import android.view.View;
27import android.view.ViewDebug;
28import android.widget.FrameLayout;
29
30import com.android.settings.R;
31
32/**
33 * Container for two-dimensional chart, drawn with a combination of
34 * {@link ChartGridView}, {@link ChartNetworkSeriesView} and {@link ChartSweepView}
35 * children. The entire chart uses {@link ChartAxis} to map between raw values
36 * and screen coordinates.
37 */
38public class ChartView extends FrameLayout {
39    // TODO: extend something that supports two-dimensional scrolling
40
41    private static final int SWEEP_GRAVITY = Gravity.TOP | Gravity.LEFT;
42
43    ChartAxis mHoriz;
44    ChartAxis mVert;
45
46    @ViewDebug.ExportedProperty
47    private int mOptimalWidth = -1;
48    private float mOptimalWidthWeight = 0;
49
50    private Rect mContent = new Rect();
51
52    public ChartView(Context context) {
53        this(context, null, 0);
54    }
55
56    public ChartView(Context context, AttributeSet attrs) {
57        this(context, attrs, 0);
58    }
59
60    public ChartView(Context context, AttributeSet attrs, int defStyle) {
61        super(context, attrs, defStyle);
62
63        final TypedArray a = context.obtainStyledAttributes(
64                attrs, R.styleable.ChartView, defStyle, 0);
65        setOptimalWidth(a.getDimensionPixelSize(R.styleable.ChartView_optimalWidth, -1),
66                a.getFloat(R.styleable.ChartView_optimalWidthWeight, 0));
67        a.recycle();
68
69        setClipToPadding(false);
70        setClipChildren(false);
71    }
72
73    void init(ChartAxis horiz, ChartAxis vert) {
74        mHoriz = checkNotNull(horiz, "missing horiz");
75        mVert = checkNotNull(vert, "missing vert");
76    }
77
78    public void setOptimalWidth(int optimalWidth, float optimalWidthWeight) {
79        mOptimalWidth = optimalWidth;
80        mOptimalWidthWeight = optimalWidthWeight;
81        requestLayout();
82    }
83
84    @Override
85    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
86        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
87
88        final int slack = getMeasuredWidth() - mOptimalWidth;
89        if (mOptimalWidth > 0 && slack > 0) {
90            final int targetWidth = (int) (mOptimalWidth + (slack * mOptimalWidthWeight));
91            widthMeasureSpec = MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY);
92            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
93        }
94    }
95
96    @Override
97    protected void onLayout(boolean changed, int l, int t, int r, int b) {
98        mContent.set(getPaddingLeft(), getPaddingTop(), r - l - getPaddingRight(),
99                b - t - getPaddingBottom());
100        final int width = mContent.width();
101        final int height = mContent.height();
102
103        // no scrolling yet, so tell dimensions to fill exactly
104        mHoriz.setSize(width);
105        mVert.setSize(height);
106
107        final Rect parentRect = new Rect();
108        final Rect childRect = new Rect();
109
110        for (int i = 0; i < getChildCount(); i++) {
111            final View child = getChildAt(i);
112            final LayoutParams params = (LayoutParams) child.getLayoutParams();
113
114            parentRect.set(mContent);
115
116            if (child instanceof ChartNetworkSeriesView || child instanceof ChartGridView) {
117                // series are always laid out to fill entire graph area
118                // TODO: handle scrolling for series larger than content area
119                Gravity.apply(params.gravity, width, height, parentRect, childRect);
120                child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
121
122            } else if (child instanceof ChartSweepView) {
123                layoutSweep((ChartSweepView) child, parentRect, childRect);
124                child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
125            }
126        }
127    }
128
129    protected void layoutSweep(ChartSweepView sweep) {
130        final Rect parentRect = new Rect(mContent);
131        final Rect childRect = new Rect();
132
133        layoutSweep(sweep, parentRect, childRect);
134        sweep.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
135    }
136
137    protected void layoutSweep(ChartSweepView sweep, Rect parentRect, Rect childRect) {
138        final Rect sweepMargins = sweep.getMargins();
139
140        // sweep is always placed along specific dimension
141        if (sweep.getFollowAxis() == ChartSweepView.VERTICAL) {
142            parentRect.top += sweepMargins.top + (int) sweep.getPoint();
143            parentRect.bottom = parentRect.top;
144            parentRect.left += sweepMargins.left;
145            parentRect.right += sweepMargins.right;
146            Gravity.apply(SWEEP_GRAVITY, parentRect.width(), sweep.getMeasuredHeight(),
147                    parentRect, childRect);
148
149        } else {
150            parentRect.left += sweepMargins.left + (int) sweep.getPoint();
151            parentRect.right = parentRect.left;
152            parentRect.top += sweepMargins.top;
153            parentRect.bottom += sweepMargins.bottom;
154            Gravity.apply(SWEEP_GRAVITY, sweep.getMeasuredWidth(), parentRect.height(),
155                    parentRect, childRect);
156        }
157    }
158
159}
160