• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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 
17 package org.chromium.latency.walt;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.util.AttributeSet;
22 import android.view.View;
23 import android.widget.RelativeLayout;
24 
25 import com.github.mikephil.charting.charts.BarChart;
26 import com.github.mikephil.charting.components.AxisBase;
27 import com.github.mikephil.charting.components.Description;
28 import com.github.mikephil.charting.components.XAxis;
29 import com.github.mikephil.charting.data.BarData;
30 import com.github.mikephil.charting.data.BarDataSet;
31 import com.github.mikephil.charting.data.BarEntry;
32 import com.github.mikephil.charting.formatter.IAxisValueFormatter;
33 import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
34 import com.github.mikephil.charting.utils.ColorTemplate;
35 
36 import java.text.DecimalFormat;
37 import java.util.ArrayList;
38 
39 public class HistogramChart extends RelativeLayout implements View.OnClickListener {
40 
41     static final float GROUP_SPACE = 0.1f;
42     private HistogramData histogramData;
43     private BarChart barChart;
44 
HistogramChart(Context context, AttributeSet attrs)45     public HistogramChart(Context context, AttributeSet attrs) {
46         super(context, attrs);
47         inflate(getContext(), R.layout.histogram, this);
48 
49         barChart = (BarChart) findViewById(R.id.bar_chart);
50         findViewById(R.id.button_close_bar_chart).setOnClickListener(this);
51 
52         final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HistogramChart);
53         final String descString;
54         final int numDataSets;
55         final float binWidth;
56         try {
57             descString = a.getString(R.styleable.HistogramChart_description);
58             numDataSets = a.getInteger(R.styleable.HistogramChart_numDataSets, 1);
59             binWidth = a.getFloat(R.styleable.HistogramChart_binWidth, 5f);
60         } finally {
61             a.recycle();
62         }
63 
64         ArrayList<IBarDataSet> dataSets = new ArrayList<>(numDataSets);
65         for (int i = 0; i < numDataSets; i++) {
66             final BarDataSet dataSet = new BarDataSet(new ArrayList<BarEntry>(), "");
67             dataSet.setColor(ColorTemplate.MATERIAL_COLORS[i]);
68             dataSets.add(dataSet);
69         }
70 
71         BarData barData = new BarData(dataSets);
72         barData.setBarWidth((1f - GROUP_SPACE)/numDataSets);
73         barChart.setData(barData);
74         histogramData = new HistogramData(numDataSets, binWidth);
75         groupBars(barData);
76         final Description desc = new Description();
77         desc.setText(descString);
78         desc.setTextSize(12f);
79         barChart.setDescription(desc);
80 
81         XAxis xAxis = barChart.getXAxis();
82         xAxis.setGranularityEnabled(true);
83         xAxis.setGranularity(1);
84         xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
85         xAxis.setValueFormatter(new IAxisValueFormatter() {
86             DecimalFormat df = new DecimalFormat("#.##");
87 
88             @Override
89             public String getFormattedValue(float value, AxisBase axis) {
90                 return df.format(histogramData.getDisplayValue(value));
91             }
92         });
93 
94         barChart.setFitBars(true);
95         barChart.invalidate();
96     }
97 
getBarChart()98     BarChart getBarChart() {
99         return barChart;
100     }
101 
102     /**
103      * Re-implementation of BarData.groupBars(), but allows grouping with only 1 BarDataSet
104      * This adjusts the x-coordinates of entries, which centers the bars between axis labels
105      */
groupBars(final BarData barData)106     static void groupBars(final BarData barData) {
107         IBarDataSet max = barData.getMaxEntryCountSet();
108         int maxEntryCount = max.getEntryCount();
109         float groupSpaceWidthHalf = GROUP_SPACE / 2f;
110         float barWidthHalf = barData.getBarWidth() / 2f;
111         float interval = barData.getGroupWidth(GROUP_SPACE, 0);
112         float fromX = 0;
113 
114         for (int i = 0; i < maxEntryCount; i++) {
115             float start = fromX;
116             fromX += groupSpaceWidthHalf;
117 
118             for (IBarDataSet set : barData.getDataSets()) {
119                 fromX += barWidthHalf;
120                 if (i < set.getEntryCount()) {
121                     BarEntry entry = set.getEntryForIndex(i);
122                     if (entry != null) {
123                         entry.setX(fromX);
124                     }
125                 }
126                 fromX += barWidthHalf;
127             }
128 
129             fromX += groupSpaceWidthHalf;
130             float end = fromX;
131             float innerInterval = end - start;
132             float diff = interval - innerInterval;
133 
134             // correct rounding errors
135             if (diff > 0 || diff < 0) {
136                 fromX += diff;
137             }
138         }
139         barData.notifyDataChanged();
140     }
141 
clearData()142     public void clearData() {
143         histogramData.clear();
144         for (IBarDataSet dataSet : barChart.getBarData().getDataSets()) {
145             dataSet.clear();
146         }
147         barChart.getBarData().notifyDataChanged();
148         barChart.invalidate();
149     }
150 
addEntry(int dataSetIndex, double value)151     public void addEntry(int dataSetIndex, double value) {
152         histogramData.addEntry(barChart.getBarData(), dataSetIndex, value);
153         recalculateXAxis();
154     }
155 
addEntry(double value)156     public void addEntry(double value) {
157         addEntry(0, value);
158     }
159 
recalculateXAxis()160     private void recalculateXAxis() {
161         final XAxis xAxis = barChart.getXAxis();
162         xAxis.setAxisMinimum(0);
163         xAxis.setAxisMaximum(histogramData.getNumBins());
164         barChart.notifyDataSetChanged();
165         barChart.invalidate();
166     }
167 
setLabel(int dataSetIndex, String label)168     public void setLabel(int dataSetIndex, String label) {
169         barChart.getBarData().getDataSetByIndex(dataSetIndex).setLabel(label);
170         barChart.getLegendRenderer().computeLegend(barChart.getBarData());
171         barChart.invalidate();
172     }
173 
setLabel(String label)174     public void setLabel(String label) {
175         setLabel(0, label);
176     }
177 
setDescription(String description)178     public void setDescription(String description) {
179         getBarChart().getDescription().setText(description);
180     }
181 
setLegendEnabled(boolean enabled)182     public void setLegendEnabled(boolean enabled) {
183         barChart.getLegend().setEnabled(enabled);
184         barChart.notifyDataSetChanged();
185         barChart.invalidate();
186     }
187 
188     @Override
onClick(View v)189     public void onClick(View v) {
190         switch (v.getId()) {
191             case R.id.button_close_bar_chart:
192                 this.setVisibility(GONE);
193         }
194     }
195 
196     static class HistogramData {
197         private float binWidth;
198         private final ArrayList<ArrayList<Double>> rawData;
199         private double minBin = 0;
200         private double maxBin = 100;
201         private double min = 0;
202         private double max = 100;
203 
HistogramData(int numDataSets, float binWidth)204         HistogramData(int numDataSets, float binWidth) {
205             this.binWidth = binWidth;
206             rawData = new ArrayList<>(numDataSets);
207             for (int i = 0; i < numDataSets; i++) {
208                 rawData.add(new ArrayList<Double>());
209             }
210         }
211 
getBinWidth()212         float getBinWidth() {
213             return binWidth;
214         }
215 
getMinBin()216         double getMinBin() {
217             return minBin;
218         }
219 
clear()220         void clear() {
221             for (int i = 0; i < rawData.size(); i++) {
222                 rawData.get(i).clear();
223             }
224         }
225 
isEmpty()226         private boolean isEmpty() {
227             for (ArrayList<Double> data : rawData) {
228                 if (!data.isEmpty()) return false;
229             }
230             return true;
231         }
232 
addEntry(BarData barData, int dataSetIndex, double value)233         void addEntry(BarData barData, int dataSetIndex, double value) {
234             if (isEmpty()) {
235                 min = value;
236                 max = value;
237             } else {
238                 if (value < min) min = value;
239                 if (value > max) max = value;
240             }
241 
242             rawData.get(dataSetIndex).add(value);
243             recalculateDataSet(barData);
244         }
245 
recalculateDataSet(final BarData barData)246         void recalculateDataSet(final BarData barData) {
247             minBin = Math.floor(min / binWidth) * binWidth;
248             maxBin = Math.floor(max / binWidth) * binWidth;
249 
250             int[][] bins = new int[rawData.size()][getNumBins()];
251 
252             for (int setNum = 0; setNum < rawData.size(); setNum++) {
253                 for (Double d : rawData.get(setNum)) {
254                     ++bins[setNum][(int) (Math.floor((d - minBin) / binWidth))];
255                 }
256             }
257 
258             for (int setNum = 0; setNum < barData.getDataSetCount(); setNum++) {
259                 final IBarDataSet dataSet = barData.getDataSetByIndex(setNum);
260                 dataSet.clear();
261                 for (int i = 0; i < bins[setNum].length; i++) {
262                     dataSet.addEntry(new BarEntry(i, bins[setNum][i]));
263                 }
264             }
265             groupBars(barData);
266             barData.notifyDataChanged();
267         }
268 
getNumBins()269         int getNumBins() {
270             return (int) (((maxBin - minBin) / binWidth) + 1);
271         }
272 
getDisplayValue(float value)273         double getDisplayValue(float value) {
274             return value * getBinWidth() + getMinBin();
275         }
276     }
277 }
278